public/Organizations.ps1
# Meraki Organization functions using namespace System.Collections.Generic function Set-MerakiAPI() { [CmdletBinding()] Param( [string]$APIKey, [string]$OrgID, [string]$ProfileName, [switch]$SecureKey ) function Set-MerakiSecret() { Param( [string]$APIKey ) $SecretIn = @{ Version = 1 APIKey = $APIKey } $Secret = $SecretIn | ConvertTo-Json $Params = @{ Name = "MerakiAPI" Secret = $Secret } Set-Secret @Params } $configPath = "{0}/.meraki" -f $HOME $configFile = "{0}/config.json" -f $configPath if (-not (Test-Path -Path $configFile)) { if (-not $APIKey) { $APIKey = Read-Host -Prompt "API Key: " } if ($APIKey) { if ($SecureKey) { $config = @{ APIKey = "Secure" } $Params = @{ APIKey = $APIKey } Set-MerakiSecret @Params } else { $config = @{ APIKey = $apiKey } } } else { Throw "APIKey required if config file does not exist!" } if ((-not $OrgId) -and (-not $profileName)) { $orgs = Get-MerakiOrganizations -APIKey $APIKey $config.Add('profiles', @{default = $orgs[0].Id}) foreach ($org in $orgs) { $config.profiles.Add($org.name, $org.id) } } else { if (-not $profileName) { $config.Add('profiles',@{default = $OrgID}) } else { $config.Add('profiles',@{}) $config.profiles.Add($profileName, $OrgID) } } } else { # Read config into an object $oConfig = Get-Content -Raw -Path $configFile | ConvertFrom-Json # Convert the object to a hash table $config = $oConfig | ConvertTo-HashTable if ($APIKey) { If ($config.APIKey -ne $APIKey) { Write-Host "The APIKey you entered does not match the APIKey in the config file. This will overwrite the existing config file!" -ForegroundColor Yellow $response = ($R = read-host "Continue? [Y/n]:") ? $R : 'Y' if ($response-eq "Y") { if ($SecureKey) { $config = @{ APIKey = "Secure" } $VaultName = (Get-SecretVault | Select-Object -First 1).Name If ($VaultName) { Write-Host "Checking $VaultName for exiting Meraki Secret" $Secret = Get-SecretInfo -Vault $VaultName | Where-Object {$_.Name -eq 'MerakiAPI'} $response = Read-Host "MerakiAPI secret already exists, Do you want to overwrite? [y/N]" if ($response -ne 'y') { Write-Host "Aborting!" exit } } Set-MerakiSecret -APIKey $APIKey } else { $config = @{ APIKey = $APiKey } } } else { Throw "Aborting!" } } else { if ($SecureKey) { $Config.APIKey = "Secure" Set-MerakiSecret -APIKey $APIKey } } } else { if ($SecureKey) { $APIKey = $config.APIKey $config.APIKey = "Secure" Set-MerakiSecret -APIKey $APIKey if (-not (Test-Path -Path $configPath)) { [void](New-Item -Path $configPath -ItemType:Directory) } $config | ConvertTo-Json | Out-File -FilePath $configFile return } } if ((-not $OrgID) -and (-not $profileName)) { $response = ($R = read-host "Overwrite Profiles from organization names? [Y/n]:") ? $R : 'Y' if ($response -eq 'Y') { $orgs = Get-MerakiOrganizations -APIKey $config.APIKey $config.Add('profiles', @{default = $orgs[0].Id}) foreach ($org in $orgs) { $config.profiles.Add($org.name, $org.Id) } } } else { if (-not $profileName) { if ($config.profiles.default) { $config.profiles.default = $orgId } else { $config.profiles.Add('default', $orgId) } } else { if ($config.profiles.$profileName) { $config.profiles.$profileName = $OrgId } else { $config.profiles.Add($profileName, $OrgId) } } } } if (-not (Test-Path -Path $configPath)) { [void](New-Item -Path $configPath -ItemType:Directory) } $config | ConvertTo-Json | Out-File -FilePath $configFile <# .SYNOPSIS Set the configuration file. .DESCRIPTION Sets up the configuration file. This can be the initial configuration or creating named profiles. .PARAMETER APIKey Your Meraki API key. If a configuration file exists and this key does not match the key in the file a new file will be created overwriting the existing file. .PARAMETER OrgID The ID of an organization to add to the profile. .PARAMETER profileName The name of the profile to create. If omitted the OrgID is set as the default profile. .PARAMETER SecureKey Save the API key to the a secret Vault. If this parameter is provided alone, your existing API key will be stored in the secret vault and the configuration changed to support the secure key. The config.json file will no longer contain your key in clear text. If you do not have a secrets vault registered you must register on using the New-MerakiSecretsVault function. If your Secrets Management configuration requires a password you will only eed to enter the password when issuing the first command. The vault will remain unlocked for the remainder of the PowerShell session. .NOTES If the OrgID and profileName parameters are omitted named profiles will be created based on the Organization names pulled from Meraki. This approach may not be the best as most of the time these names will have multiple words and spaces and just be too long. .EXAMPLE Create the default profile PS> Set-MerakiAPI -APIKey 'EXAMPLEGDTE63534HD74BD93847' -OrgId 123456 .EXAMPLE Create a Named Profile. Set-MerakiAPI -OrgId 987458 -ProfileName USNetwork .EXAMPLE Create a secure key profile. Set-MerakiAPI -APIKey 'EXAMPLE88fhjryh4666drfe9207' -OrgId 123456 -SecureKey .EXAMPLE Convert your existing configuration to use the Secure Key Store. Set-MerakiAPI -SecureKey #> } function Set-MerakiProfile () { Param( [Parameter(Mandatory = $true)] [string]$profileName ) $configFile = "{0}/.meraki/config.json" -f $home $Config = Get-Content -Path $configFile | ConvertFrom-Json | ConvertTo-HashTable $orgID = $Config.profiles.$profileName if (-not $OrgId) { throw "Invalid profile name!" } Set-MerakiAPI -OrgID $orgID -profileName 'default' <# .SYNOPSIS Set the default profile to the specified named profile. .DESCRIPTION Use this function to set the default profile for all subsequent command. This changes the organization ID of the default profile so any future commands will use this profile even after closing out of PowerShell. .PARAMETER profileName The name of the profile to use. #> } function Get-MerakiOrganizations() { Param( [string]$APIKey ) $Uri = "{0}/organizations" -f $BaseURI If ($APIKey) { $Headers = @{ "X-Cisco-Meraki-API-Key" = $APIKey "Content-Type" = 'application/json' } } else { $Headers = Get-Headers } try { $response = Invoke-RestMethod -Method GET -Uri $Uri -Headers $Headers -PreserveAuthorizationOnRedirect return $response } catch { throw $_ } <# .SYNOPSIS Get Meraki Organizations .DESCRIPTION Get all Meraki Organizations your API Key has access to. .PARAMETER APIKey Meraki API Key. #> } Set-Alias -Name GMOrgs -Value Get-MerakiOrganizations -Option ReadOnly function Get-MerakiOrganization() { [CmdLetBinding(DefaultParameterSetName = 'default')] Param ( [Parameter(ParameterSetName = 'org')] [string]$OrgID, [Parameter(ParameterSetName = 'profile')] [string]$profileName ) <# If ($OrgId -and $profileName) { Write-Host "The parameters OrgId and ProfileName cannot be used together!" -ForegroundColor Red return } #> if (-not $OrgID) { $config = Read-Config if ($profileName) { $OrgId = $config.profiles.$profileName if (-not $OrgId) { throw "Invalid profile name!" } } else { $OrgId = $config.profiles.default if (-not $OrgId) { throw "There is no default profile. You must use the -OrgId parameter and supply the Organization Id." } } } $Uri = "{0}/organizations/{1}" -f $BaseURI, $OrgId $Headers = Get-Headers try { $response = Invoke-RestMethod -Method GET -Uri $Uri -Headers $Headers -PreserveAuthorizationOnRedirect return $response } catch { throw $_ } <# .SYNOPSIS Get Meraki Organization .PARAMETER OrgId The organization ID .PARAMETER profileName Use the profile name to get organization. .OUTPUTS A Meraki organization Object #> } Set-Alias -Name GMOrg -value Get-MerakiOrganization -Option ReadOnly function New-MerakiOrganization() { [CmdletBinding()] Param( [Parameter(Mandatory = $true)] [string]$Name, [string]$ManagementName, [string]$ManagementValue ) $Headers = Get-Headers $Uri = "{0}/organizations" -f $BaseURI $_Body = @{ name = $Name } If ($ManagementName) { $_Body.Add("management", @{ details = @( @{ "name" = $ManagementName "value" = $ } ) }) } $body = $_Body | ConvertTo-Json -Depth 5 -Compress try { $response = Invoke-RestMethod -Method POST -Uri $Uri -Headers $Headers -Body $body -PreserveAuthorizationOnRedirect return $response } catch { throw $_ } <# .SYNOPSIS Create an organization .DESCRIPTION Create a new Meraki Organization .PARAMETER Name The name of the organization .PARAMETER ManagementName Name of the management system .PARAMETER ManagementValue Value of the management system #> } function Set-MerakiOrganization() { [CmdletBinding(DefaultParameterSetName = 'default')] Param( [Parameter(Mandatory = $true)] [string]$Name, [string]$ManagementName, [string]$ManagementValue, [switch]$ApiEnabled, [Parameter(ParameterSetName = 'org')] [string]$OrgID, [Parameter(ParameterSetName = 'profile')] [string]$profileName ) <# If ($OrgId -and $profileName) { Write-Host "The parameters OrgId and ProfileName cannot be used together!" -ForegroundColor Red return } #> if (-not $orgID) { $config = Read-Config if ($profileName) { $orgID = $config.profiles.$profileName if (-not $orgID) { throw "Invalid profile name!" } } else { $orgID = $config.profiles.default } } $Uri = "{0}/organizations/{1}" -f $BaseURI, $orgID $Headers = Get-Headers $_Body = @{ name = $Name } if ($ManagementName) { $_Body.Add("management", @{ details = @( @{ name = $ManagementName value = $ManagementValue } ) }) } if ($ApiEnabled) { $_Body.Add("api", @{ "enabled" = $ApiEnabled.IsPresent }) } $body = $_Body | ConvertTo-Json -Depth 5 -Compress try { $response = Invoke-RestMethod -Method PUT -Uri $Uri -Headers $Headers -Body $body -PreserveAuthorizationOnRedirect return $response } catch { throw $_ } <# .SYNOPSIS Update an organization .DESCRIPTION Update a Meraki Organization .PARAMETER OrgID The organization Id .PARAMETER profileName The saved profile name .PARAMETER Name The name of the organization .PARAMETER ManagementName Name of the management data .PARAMETER ManagementValue Value of the management data .PARAMETER ApiEnabled If present, enable the access to the Cisco Meraki Dashboard API .OUTPUTS A Meraki organization object #> } #region Organization Networks function Get-MerakiNetworks() { [CmdletBinding(DefaultParameterSetName = 'default')] Param( [string]$ConfigTemplateId, [switch]$IsBoundToConfigTemplate, [switch]$IncludeTemplates, [Parameter(ParameterSetName = 'org')] [string]$OrgID, [Parameter(ParameterSetName = 'profile')] [string]$profileName ) <# If ($OrgId -and $profileName) { Write-Host "The parameters OrgId and ProfileName cannot be used together!" -ForegroundColor Red return } #> if (-not $orgID) { $config = Read-Config if ($profileName) { $orgID = $config.profiles.$profileName if (-not $orgID) { throw "Invalid profile name!" } } else { $orgID = $config.profiles.default } } $Uri = "{0}/organizations/{1}/networks" -f $BaseURI, $orgID if ($ConfigTemplateId){ $Uri = "{0}?configTemplateId=" -f $Uri, $ConfigTemplateId } If ($IsBoundToConfigTemplate.IsPresent) { if ($Uri.Contains("?")) { $Uri = "{0}&isBoundToConfigTemplate=true" -f $Uri } else { $Uri = "{0}?isBoundToConfigTemplate=false" -f $Uri } } $Headers = Get-Headers try { $response = Invoke-RestMethod -Method GET -Uri $Uri -Headers $Headers -PreserveAuthorizationOnRedirect If ($IncludeTemplates.IsPresent) { $templates = @{} Get-MerakiOrganizationConfigTemplates | ForEach-Object { $templates.Add($_.id, $_) } foreach ($network in $response) { If ($Network.isBoundToConfigTemplate) { $template = $templates[$Network.configTemplateId] $response[$response.IndexOf($Network)] | Add-Member -MemberType NoteProperty -Name "configTemplate" -Value $template } } } return $response } catch { throw $_ } <# .SYNOPSIS Get all Meraki Networks. .DESCRIPTION Get all Meraki networks in an organization. .PARAMETER OrgID The Organization ID. .PARAMETER profileName The profile name to use to get networks. .PARAMETER IncludeTemplates Includes a configTemplate property containing the configuration template. .PARAMETER ConfigTemplateId Get all networks bound to this template Id. .PARAMETER IsBoundToConfigTemplate Get only networks bound to config templates. .OUTPUTS An array of Meraki network objects. #> } Set-Alias -Name GMNets -Value Get-MerakiNetworks -Option ReadOnly function Add-MerakiNetwork() { [CmdletBinding(DefaultParameterSetName = 'default')] Param( [Parameter(Mandatory = $true)] [string]$Name, [Parameter(Mandatory = $true)] [string[]]$ProductTypes, [string]$TimeZone, [string]$Notes, [string[]]$Tags, [string]$CopyFromNetworkId, [Parameter(ParameterSetName = 'org')] [string]$OrgID, [Parameter(ParameterSetName = 'profile')] [string]$profileName ) <# If ($OrgId -and $profileName) { Write-Host "The parameters OrgId and ProfileName cannot be used together!" -ForegroundColor Red return } #> if (-not $orgID) { $config = Read-Config if ($profileName) { $orgID = $config.profiles.$profileName if (-not $orgID) { throw "Invalid profile name!" } } else { $orgID = $config.profiles.default } } $Headers = Get-Headers $Uri = "{0}/organizations/{1}/networks" $_Body = @{ name = $Name productTypes = $ProductTypes } if ($TimeZone) { $_Body.Add("timeZone", $TimeZone) } if ($Notes) { $_Body.Add("notes", $Notes) } if ($Tags) { $_Body.Add("tags", $Tags) } if ($CopyFromNetworkId) { $_Body.Add("copyFromNetworkId", $CopyFromNetworkId) } $body = $_Body | ConvertTo-Json -Depth 5 -Compress try { $response = Invoke-RestMethod -Method POST -Uri $Uri -Headers $Headers -Body $body -PreserveAuthorizationOnRedirect return $response } catch { throw $_ } <# .SYNOPSIS Create a network .DESCRIPTION Create a network in an organization .PARAMETER Name The name of the network .PARAMETER ProductTypes The product type(s) of the new network. If more than one type is included, the network will be a combined network. .PARAMETER TimeZone Time zone name from the ICANN tz database .PARAMETER Notes Add any notes or additional information about this network here. .PARAMETER Tags A list of tags to be applied to the network .PARAMETER CopyFromNetworkId The ID of the network to copy configuration from. Other provided parameters will override the copied configuration, except type which must match this network's type exactly. .PARAMETER OrgId Optional Organizational ID. .PARAMETER profileName Optional Profile name. .OUTPUTS An object containing the new network. #> } #endregion function Get-MerakiOrganizationConfigTemplates() { [CmdletBinding(DefaultParameterSetName = 'default')] Param( [Parameter(ParameterSetName = 'org')] [string]$OrgID, [Parameter(ParameterSetName = 'default')] [string]$profileName ) <# If ($OrgId -and $profileName) { Write-Host "The parameters OrgId and ProfileName cannot be used together!" -ForegroundColor Red return } #> if (-not $OrgID) { $config = Read-Config if ($profileName) { $OrgID = $config.profiles.$profileName if (-not $OrgID) { throw "Invalid profile name!" } } else { $OrgID = $config.profiles.default } } $Uri = "{0}/organizations/{1}/configTemplates" -f $BaseURI, $OrgID $headers = Get-Headers try{ $response = Invoke-RestMethod -Method GET -Uri $Uri -Headers $headers -PreserveAuthorizationOnRedirect return $response } catch { throw $_ } <# .SYNOPSIS Get the Organization Configuration Templates .DESCRIPTION Get the configuration templates for a given organization. .PARAMETER OrgID The Organization Id. If omitted uses the default profile. .PARAMETER profileName The profile name to use to get the templates. .OUTPUTS An array of Meraki configuration template objects. #> } function Get-MerakiOrganizationConfigTemplate () { [CmdletBinding(DefaultParameterSetName = 'default')] Param( [Parameter( ValueFromPipelineByPropertyName )] [Alias('TemplateId')] [string]$Id, [Parameter(ParameterSetName = 'org')] [string]$OrgID, [Parameter(ParameterSetName = 'profile')] [string]$profileName ) Begin { If ($OrgId -and $profileName) { Write-Host "The parameters OrgId and ProfileName cannot be used together!" -ForegroundColor Red return } if (-not $OrgID) { $config = Read-Config if ($profileName) { $OrgID = $config.profiles.$profileName if (-not $OrgID) { throw "Invalid profile name!" } } else { $OrgID = $config.profiles.default } } $Headers = Get-Headers } Process { $Uri = "{0}/organizations/{1}/configTemplates" -f $BaseURI, $OrgID if ($Id) { $Uri = "{0}/{1}" -f $Uri, $Id } try { $response = Invoke-RestMethod -Method GET -Uri $Uri -Headers $Headers -PreserveAuthorizationOnRedirect return $response } catch { throw $_ } } <# .SYNOPSIS Retrieve a configuration Template .DESCRIPTION Retrieves the configuration template designated by the provided template Id. .PARAMETER Id The ID of the template to retrieve. .PARAMETER OrgID Optional Organization ID. .PARAMETER profileName Optional Profile Name .OUTPUTS A configuration template object. #> } Set-Alias -Name GMOrgTemplates -value Get-MerakiOrganizationConfigTemplate -Option ReadOnly Set-Alias -Name Get-MerakiOrganizationConfigTemplates -Value Get-MerakiOrganizationConfigTemplate function Get-MerakiOrganizationDevices() { [CmdletBinding(DefaultParameterSetName = 'default')] Param( [Parameter(ParameterSetName = 'org')] [string]$OrgID, [Parameter(ParameterSetName = 'profile')] [string]$profileName ) <# If ($OrgId -and $profileName) { Write-Host "The parameters OrgId and ProfileName cannot be used together!" -ForegroundColor Red return } #> If (-not $OrgID) { $config = Read-Config if ($profileName) { $OrgID = $config.profiles.$profileName if (-not $OrgID) { throw "Invalid profile name!" } } else { $OrgID = $config.profiles.default } } $Uri = "{0}/organizations/{1}/devices" -f $BaseURI, $OrgID $Headers = Get-Headers try { $response = Invoke-RestMethod -Method GET -Uri $Uri -Headers $Headers -PreserveAuthorizationOnRedirect return $response } catch { throw $_ } <# .SYNOPSIS Get organization Devices. .DESCRIPTION Get all devices in an organization. .PARAMETER OrgID Optional Organization Id.. .PARAMETER profileName Optional Profile name. .OUTPUTS AN array of Meraki Device objects. #> } Set-Alias -Name GMOrgDevs -Value Get-MerakiOrganizationDevices -Option ReadOnly function Get-MerakiOrganizationAdmins() { [CmdletBinding(DefaultParameterSetName = 'default')] Param( [Parameter(ParameterSetName = 'org')] [string]$OrgID, [Parameter(ParameterSetName = 'profile')] [string]$profileName ) <# If ($OrgId -and $profileName) { Write-Host "The parameters OrgId and ProfileName cannot be used together!" -ForegroundColor Red return } #> If (-not $orgID) { $config = Read-Config if ($profileName) { $OrgID = $config.profiles.$profileName if (-noy $OrgID) { throw "Invalid profile name!" } } else { $OrgID = $config.profiles.default } } $Uri = "{0}/organizations/{1}/admins" -f $BaseURI, $OrgID $Headers = Get-Headers try { $response = Invoke-RestMethod -Method GET -Uri $uri -Headers $Headers -PreserveAuthorizationOnRedirect return $response }catch { throw $_ } <# .SYNOPSIS Get Organization Admins. .PARAMETER OrgID Optional Organization Id. .PARAMETER profileName Optional Profile Name. .OUTPUTS An array of Meraki Admin objects. #> } Set-Alias -Name GMOrgAdmins -Value Get-MerakiOrganizationAdmins -Option ReadOnly function Get-MerakiOrganizationConfigurationChanges() { [CmdletBinding(DefaultParameterSetName='default')] Param( [Parameter(ParameterSetName = 'dates')] [Parameter(ParameterSetName = 'datesWithOrg')] [Parameter(ParameterSetName = 'datesWithProfile')] [ValidateScript({$_ -is [DateTime]})] [Alias('StartTime')] [datetime]$StartDate, [Parameter(ParameterSetName = 'dates', Mandatory)] [Parameter(ParameterSetName = 'datesWithOrg', Mandatory)] [Parameter(ParameterSetName = 'datesWithProfile', Mandatory)] [ValidateScript({$_ -is [DateTime]})] [Alias('EndTime')] [DateTime]$EndDate, [Parameter(ParameterSetName = 'days', Mandatory)] [Parameter(ParameterSetName = 'daysWithOrg', Mandatory)] [Parameter(ParameterSetName = 'daysWithProfile', Mandatory)] [ValidateScript({$_ -is [int32]})] [Alias('TimeSpan')] [ValidateRange(0,31)] [Int]$Days, [ValidateScript({$_ -is [int]})] [ValidateRange(1,1000)] [int]$PerPage, [ValidateScript({$_ -is [int]})] [ValidateRange(0,1000)] [int]$Pages = 1, [string]$NetworkID, [string]$AdminID, [Parameter(ParameterSetName = 'org', Mandatory)] [Parameter(ParameterSetName = 'daysWithOrg', Mandatory)] [Parameter(ParameterSetName = 'datesWithOrg', Mandatory)] [string]$OrgID, [Parameter(ParameterSetName = 'profile', Mandatory)] [Parameter(ParameterSetName = 'daysWithProfile', Mandatory)] [Parameter(ParameterSetName = 'datesWithProfile', Mandatory)] [string]$ProfileName ) If (-not $OrgID) { $config = Read-Config if ($profileName) { $OrgId = $config.profiles.$profileName if (-not $OrgID) { throw "Invalid profile name!" } } else { $OrgID = $config.profiles.default } } $Results = [List[PsObject]]::New() $Uri = "{0}/organizations/{1}/configurationChanges" -f $BaseURI, $OrgID $Headers = Get-Headers $psBody = @{} if ($StartTime) { $T0 = "{0:s}" -f $StartTime $psBody.Add("t0", $T0) } if ($EndTime) { $T1 = "{0:s}" -f $EndTime $psBody.add("t1", $T1) } if ($PerPage) { $psBody.Add("perPage", $PerPage) } if ($NetworkID) { $psBody.Add("networkId", $NetworkID) } if ($AdminID) { $psBody.Add("adminId", $AdminID) } $Body = $psBody | ConvertTo-Json try { $response = Invoke-WebRequest -Method GET -Uri $Uri -body $Body -Headers $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 [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 $Result.ToArray() } catch { throw $_ } <# .SYNOPSIS Get Organization Configuration Changes .DESCRIPTION Gets configuration changes made to an organization's network. .PARAMETER OrgID Optional Organization Id. .PARAMETER profileName Optional Profile Name .PARAMETER StartTime The start time to pull changes. .PARAMETER EndTime The end time to pull changes. .PARAMETER Days Number of days to pull changes .PARAMETER PerPage Number of records to pull per page. .PARAMETER Pages Number of pages to retrieve. 0 = all pages. Default is 1. .PARAMETER NetworkID Filter results by Network ID. .PARAMETER AdminID Filter results by Admin ID. .OUTPUTS An array of configuration change objects. .EXAMPLE Filter logs for last 10 days by Administrator. PS> Get-MerakiOrganizationAdmins | Where-Object {$_.Name -eq "John Doe"} | Get-MerakiOrganizationConfigurationChanges -TimeSpan 10 .EXAMPLE Filter logs for changes to the Miami network that occurred between 6/1/2020 and 6/30/2020 Get-MerakiNetworks | Where-Object {$_.Name -like "*Miami*"} | Get-MerakiOrganizationConfigurationChanges -StartTime "06/01/2020" -EndTime "06/30/2020" #> } Set-Alias -Name GMOrgCC -Value Get-MerakiOrganizationConfigurationChanges -Option ReadOnly function Get-MerakiOrganizationThirdPartyVpnPeers() { [CmdletBinding(DefaultParameterSetName = 'default')] Param( [Parameter(ParameterSetName = 'org')] [string]$OrgID, [Parameter(ParameterSetName = 'profile')] [string]$profileName ) <# If ($OrgId -and $profileName) { Write-Host "The parameters OrgId and ProfileName cannot be used together!" -ForegroundColor Red return } #> if (-not $OrgID) { $config = Read-Config if ($profileName) { $OrgId = $config.profiles.$profileName if (-not $OrgID) { throw "Invalid profile name!" } } else { $OrgId = $config.profiles.default } } $Uri = "{0}/organizations/{1}/appliance/vpn/thirdPartyVPNPeers" -f $BaseURI, $OrgID $Headers = Get-Headers try { $response = Invoke-RestMethod -Method GET -Uri $Uri -Headers $Headers -PreserveAuthorizationOnRedirect return $response.peers } catch { throw $_ } <# .SYNOPSIS Get Organization 3rd party VPNs. .PARAMETER OrgID Optional Organization Id .PARAMETER profileName Optional Profile name .OUTPUTS An array of VPN-peer objects. #> } Set-Alias -Name GMOrg3pVP -Value Get-MerakiOrganizationThirdPartyVPNPeers -Option ReadOnly function Set-MerakiOrganizationThirdPartyVpnPeer() { [CmdletBinding(DefaultParameterSetName = 'default')] Param( [Parameter(Mandatory = $true)] [string]$Name, [Parameter(Mandatory = $true)] [string]$Secret, [ValidateSet('1', '2')] [string]$IkeVersion, [ValidateSet('default', 'aws', 'azure')] [string]$IpsecPoliciesPreset, [string]$LocalId, [string]$PublicIp, [Parameter(Mandatory = $true)] [string[]]$PrivateSubnets, [string]$RemoteId, [string[]]$NetworkTags = 'all', [PSObject]$IpsecPolicies, [Parameter(ParameterSetName = 'org')] [string]$OrgID, [Parameter(ParameterSetName = 'profile')] [string]$profileName ) if (-not $OrgID) { $config = Read-Config if ($profileName) { $OrgId = $config.profiles.$profileName if (-not $OrgID) { throw "Invalid profile name!" } } else { $OrgId = $config.profiles.default } } $Headers = Get-Headers $Uri = "{0}/organizations/{1}/appliance/vpn/thirdPartyVPNPeers" -f $BaseURI, $OrgID $Peers = @{} Get-MerakiOrganizationThirdPartyVpnPeers | ForEach-Object { $Peers.Add($Name, $_) } if (-not $Peers[$Name]) { throw "Peer $Name is not found!" } if ($IkeVersion) { $Peers[$Name].ikeVersion = $IkeVersion } if ($IpsecPoliciesPreset) { $Peers[$Name].IpsecPoliciesPreset = $IpsecPoliciesPreset } if ($LocalId) { $Peers[$Name].localId = $LocalId } if ($publicIp) { $Peers[$Name].publicIp = $PublicIp } if ($RemoteId) { $Peers[$Name].remoteIp = $RemoteId } if ($Secret) { $Peers[$Name].secret = $Secret} if ($NetworkTags) { $Peer[$Name].networkTags = $NetworkTags } if ($PrivateSubnets) { $Peers[$Name].privateSubnets = $PrivateSubnets } if ($IpsecPolicies) { $Peer[$Name].ipsecPolicies = $IpsecPolicies} $NewPeers = $Peers.Values $_Body = @{ peers = $NewPeers } $Body = $_Body | ConvertTo-Json -Depth 5 -Compress try { $response = Invoke-RestMethod -Method PUT -Uri $Uri -Headers $Headers -Body $Body -PreserveAuthorizationOnRedirect return $response } catch { throw $_ } <# .DESCRIPTION Updates a third party VPN peer for an Organization .PARAMETER Name The name of the VPN peer .PARAMETER Secret The shared secret with the VPN peer .PARAMETER IkeVersion The IKE version to be used for the IPsec VPN peer configuration. Defaults to '1' when omitted. Valid values are '1', '2' .PARAMETER IpsecPoliciesPreset One of the following available presets: 'default', 'aws', 'azure'. If this is provided, the 'ipsecPolicies' parameter is ignored. .PARAMETER LocalId The local ID is used to identify the MX to the peer. This will apply to all MXs this peer applies to. .PARAMETER PublicIp The public IP of the VPN peer. .PARAMETER PrivateSubnets The list of the private subnets of the VPN peer .PARAMETER RemoteId The remote ID is used to identify the connecting VPN peer. This can either be a valid IPv4 Address, FQDN or User FQDN. .PARAMETER NetworkTags A list of network tags that will connect with this peer. Use ['all'] for all networks. Use ['none'] for no networks. If not included, the default is ['all']. .PARAMETER IpsecPolicies Custom IPSec policies for the VPN peer. If not included and a preset has not been chosen, the default preset for IPSec policies will be used. This is ab object with the following properties: childLifetime: integer The lifetime of the Phase 2 SA in seconds. ikeLifetime: integer The lifetime of the Phase 1 SA in seconds. childAuthAlgo: array[] This is the authentication algorithms to be used in Phase 2. The value should be an array with one of the following algorithms: 'sha256', 'sha1', 'md5' childCipherAlgo: array[] This is the cipher algorithms to be used in Phase 2. The value should be an array with one or more of the following algorithms: 'aes256', 'aes192', 'aes128', 'tripledes', 'des', 'null' childPfsGroup: array[] This is the Diffie-Hellman group to be used for Perfect Forward Secrecy in Phase 2. The value should be an array with one of the following values: 'disabled','group14', 'group5', 'group2', 'group1' ikeAuthAlgo: array[] This is the authentication algorithm to be used in Phase 1. The value should be an array with one of the following algorithms: 'sha256', 'sha1', 'md5' ikeCipherAlgo: array[] This is the cipher algorithm to be used in Phase 1. The value should be an array with one of the following algorithms: 'aes256', 'aes192', 'aes128', 'tripledes', 'des' ikeDiffieHellmanGroup: array[] This is the Diffie-Hellman group to be used in Phase 1. The value should be an array with one of the following algorithms: 'group14', 'group5', 'group2', 'group1' ikePrfAlgo: array[] [optional] This is the pseudo-random function to be used in IKE_SA. The value should be an array with one of the following algorithms: 'prfsha256', 'prfsha1', 'prfmd5', 'default'. The 'default' option can be used to default to the Authentication algorithm. .PARAMETER OrgID Optional Organization Id. .PARAMETER profileName Optional Profile name. #> } function New-MerakiOrganizationThirdPartyVpnPeer() { [CmdletBinding(DefaultParameterSetName = 'default')] Param( [Parameter(Mandatory = $true)] [string]$Name, [Parameter(Mandatory = $true)] [string]$Secret, [ValidateSet('1', '2')] [string]$IkeVersion, [ValidateSet('default', 'aws', 'azure')] [string]$IpsecPoliciesPreset, [string]$LocalId, [string]$PublicIp, [Parameter(Mandatory = $true)] [string[]]$PrivateSubnets, [string]$RemoteId, [string[]]$NetworkTags = 'all', [PSObject]$IpsecPolicies, [Parameter(ParameterSetName = 'org')] [string]$OrgID, [Parameter(ParameterSetName = 'profile')] [string]$profileName ) if (-not $OrgID) { $config = Read-Config if ($profileName) { $OrgId = $config.profiles.$profileName if (-not $OrgID) { throw "Invalid profile name!" } } else { $OrgId = $config.profiles.default } } $Headers = Get-Headers $Uri = "{0}/organizations/{1}/appliance/vpn/thirdPartyVPNPeers" -f $BaseURI, $OrgID $Peers = Get-MerakiOrganizationThirdPartyVPNPeers $Peer = @{ name = $Name secret = $Secret privateSubnet = $PrivateSubnets } if ($IkeVersion) { $Peer.Add("ikeVersion", $IkeVersion) } if ($IpsecPoliciesPreset) { $Peer.Add("ipsecPoliciesPreset", $IpsecPoliciesPreset) } if ($LocalId) { $Peer.Add("localId", $LocalId) } if ($PublicIp) { $Peer.Add("publicIp", $PublicIp) } if ($RemoteId) { $Peer.Add("networkTags", $NetworkTags) } if ($IpsecPolicies) { $Peer.Add("ipsecPolicies", $IpsecPolicies) } $Peers += $Peer $_Body = @{ peers = $Peers } $Body = $_Body | ConvertTo-Json -Depth 5 -Compress try { $response = Invoke-RestMethod -Method PUT -Uri $Uri -Headers $Headers -Body $Body -PreserveAuthorizationOnRedirect return $response } catch { Throw $_ } <# .DESCRIPTION Create a new organization third party VPN peer. .PARAMETER Name The name of the VPN peer .PARAMETER Secret The shared secret with the VPN peer .PARAMETER IkeVersion The IKE version to be used for the IPsec VPN peer configuration. Defaults to '1' when omitted. Valid values are '1', '2' .PARAMETER IpsecPoliciesPreset One of the following available presets: 'default', 'aws', 'azure'. If this is provided, the 'ipsecPolicies' parameter is ignored. .PARAMETER LocalId The local ID is used to identify the MX to the peer. This will apply to all MXs this peer applies to. .PARAMETER PublicIp The public IP of the VPN peer. .PARAMETER PrivateSubnets The list of the private subnets of the VPN peer .PARAMETER RemoteId The remote ID is used to identify the connecting VPN peer. This can either be a valid IPv4 Address, FQDN or User FQDN. .PARAMETER NetworkTags A list of network tags that will connect with this peer. Use ['all'] for all networks. Use ['none'] for no networks. If not included, the default is ['all']. .PARAMETER IpsecPolicies Custom IPSec policies for the VPN peer. If not included and a preset has not been chosen, the default preset for IPSec policies will be used. This is ab object with the following properties: childLifetime: integer The lifetime of the Phase 2 SA in seconds. ikeLifetime: integer The lifetime of the Phase 1 SA in seconds. childAuthAlgo: array[] This is the authentication algorithms to be used in Phase 2. The value should be an array with one of the following algorithms: 'sha256', 'sha1', 'md5' childCipherAlgo: array[] This is the cipher algorithms to be used in Phase 2. The value should be an array with one or more of the following algorithms: 'aes256', 'aes192', 'aes128', 'tripledes', 'des', 'null' childPfsGroup: array[] This is the Diffie-Hellman group to be used for Perfect Forward Secrecy in Phase 2. The value should be an array with one of the following values: 'disabled','group14', 'group5', 'group2', 'group1' ikeAuthAlgo: array[] This is the authentication algorithm to be used in Phase 1. The value should be an array with one of the following algorithms: 'sha256', 'sha1', 'md5' ikeCipherAlgo: array[] This is the cipher algorithm to be used in Phase 1. The value should be an array with one of the following algorithms: 'aes256', 'aes192', 'aes128', 'tripledes', 'des' ikeDiffieHellmanGroup: array[] This is the Diffie-Hellman group to be used in Phase 1. The value should be an array with one of the following algorithms: 'group14', 'group5', 'group2', 'group1' ikePrfAlgo: array[] [optional] This is the pseudo-random function to be used in IKE_SA. The value should be an array with one of the following algorithms: 'prfsha256', 'prfsha1', 'prfmd5', 'default'. The 'default' option can be used to default to the Authentication algorithm. .PARAMETER OrgID Optional Organization Id. .PARAMETER profileName Optional Profile name. #> } function Get-MerakiOrganizationInventoryDevices() { [CmdletBinding(DefaultParameterSetName = 'default')] Param( [Parameter(ParameterSetName = 'org')] [string]$OrgID, [Parameter(ParameterSetName = 'profile')] [string]$profileName ) <# If ($OrgId -and $profileName) { Write-Host "The parameters OrgId and ProfileName cannot be used together!" -ForegroundColor Red return } #> if (-not $OrgID) { $config = Read-Config if ($profileName) { $OrgID = $config.profiles.$profileName if (-not $OrgID) { throw "Invalid profile name!" } } else { $OrgID = $config.profiles.default } } $Uri = "{0}/organizations/{1}/inventoryDevices" -f $BaseURI, $OrgID $Headers = Get-Headers try { $response = Invoke-RestMethod -Method GET -Uri $Uri -Headers $Headers -PreserveAuthorizationOnRedirect return $response } catch { throw $_ } <# .SYNOPSIS Get the organization device inventory .PARAMETER OrgID Organization ID. If omitted used th default profile. .PARAMETER profileName Profile name to use. If omitted used th default profile. .OUTPUTS An array of inventory objects. #> } Set-Alias -Name GMOrgInvDevices -value Get-MerakiOrganizationInventoryDevices -Option ReadOnly function Get-MerakiOrganizationSecurityEvents() { [CmdLetBinding(DefaultParameterSetName='Default')] Param( [ValidateScript({$_ -is [datetime]})] [Parameter(ParameterSetName = 'dates', Mandatory)] [Parameter(ParameterSetName = 'datesWithOrg', Mandatory)] [Parameter(ParameterSetName ='datesWithProfiles', Mandatory)] [datetime]$StartDate, [ValidateScript({$_ -is [datetime]})] [Parameter(ParameterSetName = 'dates', Mandatory)] [Parameter(ParameterSetName = 'datesWithOrg', Mandatory)] [Parameter(ParameterSetName ='datesWithProfile', Mandatory)] [datetime]$EndDate, [Parameter(ParameterSetName = 'days', Mandatory)] [Parameter(ParameterSetName = 'daysWithOrg', Mandatory)] [Parameter(ParameterSetName = 'daysWithProfile', Mandatory)] [ValidateScript({$_ -is [int]})] [ValidateRange(1,31)] [int]$Days, [ValidateScript({$_ -is [int]})] [ValidateRange(3, 1000)] [int]$PerPage, [ValidateScript({$_ -is [int]})] [int]$Pages, [switch]$Descending, [Parameter(ParameterSetName = 'org', Mandatory)] [Parameter(ParameterSetName = 'datesWithOrg', Mandatory)] [Parameter(ParameterSetName = 'daysWithOrg', Mandatory)] [string]$OrgId, [Parameter(ParameterSetName = 'profile', Mandatory)] [Parameter(ParameterSetName = 'datesWithProfile', Mandatory)] [Parameter(ParameterSetName = 'daysWithProfile', Mandatory)] [string]$ProfileName ) $Headers = Get-Headers if (-not $OrgID) { $config = Read-Config if ($profileName) { $OrgID = $config.profiles.$profileName if (-not $OrgID) { throw "Invalid profile name!" } } else { $OrgID = $config.profiles.default } } $Results = [List[PsObject]]::New() $Uri = "{0}/organizations/{1}/appliance/security/events" -f $BaseURI, $OrgId Set-Variable -Name Query if ($StartDate) { $Query = "t0={0}" -f ($StartDate.ToString("0")) } if ($EndDate) { if ($Query) {$Query += "&"} $Query = "{0}t1={0}" -f $Query, ($EndDate.ToString("O")) } if ($Days) { $Seconds = [TimeSpan]::FromDays($Days).TotalSeconds if ($Query) {$Query += "&"} $Query = "{0}timestamp={1}" -f $Seconds } if ($PerPage) { if ($Query) {$Query += "&"} $Query = "{0}perPage={1}" -f $Query, $PerPage } if ($Descending) { if ($Query) {$Query += '&'} $Query = "{0}sortOrder=descending" -f $Query } if ($Query) { $Uri = "{0}?{1}" -f $Uri, $Query } try { $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 ($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 $_ } <# .SYNOPSIS Returns security event for the organization. .DESCRIPTION Returns a collection of security event objects for the given organization. Events can be filtered by dates or timespan. .PARAMETER OrgId The Organization ID. If omitted and a profile is not specified the default organization is used. Cannot be used with the profile parameter. .PARAMETER ProfileName Retrieve the Organization Id from the named profile. Cannot be used with the OrgId parameter .PARAMETER StartDate The starting date to retrieve data. Cannot be more than 365 days prior to today. .PARAMETER EndDate The ending date to retrieve data. cannot be more than 365 days after StartDate. .PARAMETER Days The number if days back from today to retrieve data, Cannot be more than 365. .PARAMETER PerPage Number of entries per page to retrieve. Acceptable range is 3-1000. Default is 100. NOTE: Paging is not implemented. .PARAMETER OrgID Organization ID. If omitted used th default profile. .PARAMETER profileName Profile name to use. If omitted used th default profile. .OUTPUTS A collection of security event objects. #> } Set-Alias -Name GMNetSecEvents -Value Get-MerakiOrganizationSecurityEvents function Get-MerakiOrganizationFirmwareUpgrades() { [CmdletBinding(DefaultParameterSetName = 'default')] Param( [switch]$IncludePending, [switch]$includeStarted, [switch]$IncludeCompleted, [switch]$IncludeCanceled, [switch]$IncludeSkipped, [switch]$IncludeAppliances, [switch]$IncludeCameras, [switch]$IncludeCellularGateways, [switch]$IncludeSensors, [switch]$IncludeSwitches, [switch]$IncludeSystemsManagers, [switch]$IncludeWireless, [ValidateScript({$_ -is [int]})] [ValidateSet(3, 1000)] [int]$PerPage, [Parameter(ParameterSetName = 'org')] [string]$OrgId, [Parameter(ParameterSetName = 'profile')] [string]$ProfileName ) Begin { if (-not $OrgID) { $config = Read-Config if ($profileName) { $OrgID = $config.profiles.$profileName if (-not $OrgID) { throw "Invalid profile name!" } } else { $OrgID = $config.profiles.default } } $Headers = Get-Headers if ($PerPage) { $Query = "perPage={0}" -f $PerPage } $Statuses = [List[string]]::New() if ($IncludePending) {$Statuses.Add("pending")} if ($includeStarted) {$Statuses.Add("started")} if ($IncludeCanceled) {$Statuses.Add("started")} if ($IncludeSkipped) {$Statuses.Add("skipped")} if ($IncludeCompleted) {$Statuses.Add("completed")} if ($Statuses.Count -gt 0) { if ($Query) {$Query += "&"} $Query = "{0}status[]={1}" -f $Query, ($Statuses.ToArray() -join ',') } $ProductTypes = [List[string]]::New() if ($IncludeAppliances) {$ProductTypes.Add("appliance")} if ($IncludeCameras) {$ProductTypes.Add("camera")} if ($IncludeCellularGateways) {$ProductTypes.Add("cellularGateway")} if ($IncludeSensors) {$ProductTypes.Add("sensors")} if ($IncludeSwitches) {$ProductTypes.Add("switch")} if ($IncludeSystemsManagers) {$ProductTypes.Add("systemsManager")} if ($IncludeWireless) {$ProductTypes.Add("wireless")} if ($ProductTypes.Count -gt 0) { if ($Query) {$Query += "&"} else {$Query += "?"} $Query = "{0}productTypes[]={1}" -f $Query, ($ProductTypes.ToArray() -join ",") } } Process{ $Uri = "{0}/organizations/{1}/firmware/upgrades" -f $BaseURI, $OrgId if ($Query) { $URI = "{0}?{1}" -f $Uri, $Query } $Results = [List[PsObject]]::New() try { $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 ($Pages -ne 1) { $done = $false do { if ($response.RelationLink['next']) { $Uri = $response.RelationLink['next'] $response = Invoke-WebRequest -Method Get -Uri $Uri -Headers $Header -PreserveAuthorizationOnRedirect [List[PsObject]]$result = $response.Content | ConvertFrom-Json if ($result) { $Result.AddRange($result) } $page += 1 if ($page -gt $Pages) { $done = $true } } else { $done = $true } } until ($done) } return $Results.ToArray() } catch { throw $_ } } <# .SYNOPSIS Get firmware upgrade information. .DESCRIPTION Get firmware upgrade information for an organization .PARAMETER OrgId The Organization Id. If omitted the retrieved from the default profile. .PARAMETER ProfileName The profile to retrieve the the Organization ID from. .PARAMETER Status Filter by this status. .PARAMETER ProductType Filter by this product type. .PARAMETER OrgID Organization ID. If omitted used th default profile. .PARAMETER profileName Profile name to use. If omitted used th default profile. #> } Set-Alias -Name GMOFirmwareUpgrades -Value Get-MerakiOrganizationFirmwareUpgrades function Get-MerakiOrganizationFirmwareUpgradesByDevice() { [CmdletBinding(DefaultParameterSetName='default')] Param( [ValidateScript({$_ -is [int]})] [ValidateRange(3,1000)] [int]$PerPage, [ValidateScript({$_ -is [int]})] [int]$Pages=1, [Parameter(ParameterSetName = 'org')] [string]$OrgId, [Parameter(ParameterSetName = 'profile')] [string]$ProfileName ) <# If ($OrgId GMOrgDevsand $profileName) { Write-Host "The parameters OrgId and ProfileName cannot be used together!" -ForegroundColor Red return } #> Begin { if (-not $OrgID) { $config = Read-Config if ($profileName) { $OrgID = $config.profiles.$profileName if (-not $OrgID) { throw "Invalid profile name!" } } else { $OrgID = $config.profiles.default } } $Headers = Get-Headers $Uri = "{0}/organizations/{1}/firmware/upgrades/byDevice" -f $BaseURI, $OrgId } Process { if ($PerPage) { $Uri = "{0}?perPage={1}" -f $Uri, $PerPage } $Results = [List[PsObject]]::New() Try { $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 ($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.ToArray() } catch { throw $_ } } <# .SYNOPSIS Get firmware upgrades by Device .DESCRIPTION Get Meraki organization firmware upgrades by device .PARAMETER OrgId Optional organization Id .PARAMETER ProfileName Optional profile name. .PARAMETER Pages Number of pages to return. Default is all. .PARAMETER PerPage Number of entries per page. .PARAMETER OrgID Organization ID. If omitted used th default profile. .PARAMETER profileName Profile name to use. If omitted used th default profile. .OUTPUTS An array of firmware upgrade objects. #> } function Get-MerakiOrganizationDeviceUplinks() { [CmdletBinding(DefaultParameterSetName = 'default')] Param( [Parameter(ValueFromPipelineByPropertyName)] [Alias('NetworkId')] [string]$Id, [Parameter(ValueFromPipelineByPropertyName)] [string]$Serial, [ValidateScript({$_ -is [int]})] [ValidateRange(3,1000)] [int]$PerPage, [ValidateScript({$_ -is [int]})] [int]$Pages, [switch]$IncludeAppliances, [switch]$IncludeCameras, [switch]$IncludeCellularGateways, [switch]$IncludeSensors, [switch]$IncludeSwitches, [switch]$IncludeSystemsManagers, [switch]$IncludeWireless, [string[]]$Tags, [ValidateSet("withAllTags","withAnyTags")] [string]$TagFilterType, [Parameter(ParameterSetName = 'org')] [string]$OrgId, [Parameter(ParameterSetName = 'profile')] [string]$ProfileName ) Begin { if (-not $OrgID) { $config = Read-Config if ($profileName) { $OrgID = $config.profiles.$profileName if (-not $OrgID) { throw "Invalid profile name!" } } else { $OrgID = $config.profiles.default } } $Headers = Get-Headers $NetworkIds = [List[string]]::New() $Serials = [List[string]]::New() if ($PerPage) { $Query = "?perPage={0}" -f $PerPage } $ProductTypes = [List[string]]::New() if ($IncludeAppliances) {$ProductTypes.Add("appliance")} if ($IncludeCameras) {$ProductTypes.Add("camera")} if ($IncludeCellularGateways) {$ProductTypes.Add("cellularGateway")} if ($IncludeSensors) {$ProductTypes.Add("sensors")} if ($IncludeSwitches) {$ProductTypes.Add("switch")} if ($IncludeSystemsManagers) {$ProductTypes.Add("systemsManager")} if ($IncludeWireless) {$ProductTypes.Add("wireless")} if ($ProductTypes.Count -gt 0) { if ($Query) {$Query += "&"} else {$Query += "?"} $Query = "{0}productTypes[]={1}" -f $Query, ($ProductTypes.ToArray() -join ',') } if ($Tags) { if ($Query) {$Query += "&"} else {$Query += "?"} $Query = "{0}tags[]={1}" -f $Query, ($Tags -join ',') if ($TagFilterType) { $Query = "{0}&tagFilterType={1}" -f $Query, $TagFilterType } } $Uri = "{0}/organizations/{1}/devices/uplinks/addresses/byDevice" -f $BaseURI, $OrgId } Process { if ($id) { $NetworkIds.Add($Id) } if ($Serial) { $Serials.Add($Serial) } if ($Serials.Count -gt 0) { if ($Query) {$Query += "&"} else {$Query += "?"} $Query = "{0}serials[]={1}" -f $Query, ($NetworkIds.ToArray() -join ',') } if ($Query) { $Uri = "{0}{1}" -f $Uri, $Query } $Results = [List[PsObject]]::New() try { $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 ($Pages -ne 1) { $done = $false do { if ($response.RelationLink['next']) { $Uri = $response.RelationLink['next'] $response = Invoke-WebRequest -Method Get -Uri $Uri -Headers $Headers [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) } $Networks = @{} Get-MerakiNetworks | ForEach-Object{ $Networks.Add($_.id, $_.name) } $Results | ForEach-Object { $NetworkName = $Networks[$_.network.id] $_.network | Add-Member -MemberType NoteProperty -Name Name -Value $NetworkName } return $Results.ToArray() } catch { throw $_ } } <# .SYNOPSIS List the current uplink addresses for devices in an organization. .DESCRIPTION List the current uplink addresses for devices in an organization. This can be filtered by Networks, Product Types, Serials, and Tags. .PARAMETER Id Optional parameter to filter device uplinks by network ID. .PARAMETER Serial Optional parameter to filter device uplinks by device product types. .PARAMETER PerPage The number of entries per page returned. Acceptable range is 3 - 1000. Default is 1000 .PARAMETER Pages Number of pages to return. Default is 1, 0 = all pages. .PARAMETER IncludeAppliances Include Appliances .PARAMETER IncludeCameras Include Cameras .PARAMETER IncludeCellularGateways Include Cellular Gateways .PARAMETER IncludeSensors Include Sensors .PARAMETER IncludeSwitches Include Switches .PARAMETER IncludeSystemsManagers Include Systems Managers .PARAMETER IncludeWireless Include Wireless .PARAMETER Tags An optional parameter to filter devices by tags. The filtering is case-sensitive. .PARAMETER TagFilterType An optional parameter of value 'withAnyTags' or 'withAllTags' to indicate whether to return devices which contain ANY or ALL of the included tags. If no type is included, 'withAnyTags' will be selected. .PARAMETER OrgId Organization Id to use. .PARAMETER ProfileName Named profile to use. .PARAMETER OrgID Organization ID. If omitted used th default profile. .PARAMETER profileName Profile name to use. If omitted used th default profile. .OUTPUTS An array of device uplink objects. .NOTES If no include parameters are given then all product typed are returned. If one or more include parameters are given then the results are restricted to those product types. #> } function Get-MerakiOrganizationDeviceStatus() { [CmdletBinding(DefaultParameterSetName='default')] Param( [Alias('NetworkId')] [string]$Id, [string]$Serial, [Parameter(ParameterSetName = 'org')] [string]$OrgId, [Parameter(ParameterSetName = 'Profile')] [string]$ProfileName ) Begin { $Headers = Get-Headers #$NetworkIds = [List[string]]::New() #$Serials = [List[string]]::New() if (-not $OrgID) { $config = Read-Config if ($profileName) { $OrgID = $config.profiles.$profileName if (-not $OrgID) { throw "Invalid profile name!" } } else { $OrgID = $config.profiles.default } } $Uri = "{0}/organizations/{1}/devices/statuses" -f $BaseUri, $OrgId } Process { $Params = @{ Method = "Get" Uri = $Uri Headers = $Headers } $Results = [List[PsObject]]::New() try { $response = Invoke-WebRequest @Params -PreserveAuthorizationOnRedirect $Results = $response.Content | ConvertFrom-Json $Networks = @{} Get-MerakiNetworks | ForEach-Object { $Networks.Add($_.Id, $_) } $Results | ForEach-Object { $NetworkName = $Networks[$_.NetworkId].Name $_ | Add-Member -MemberType NoteProperty -Name "NetworkName" -Value $NetworkName } return $Results } catch { throw $_ } } <# .SYNOPSIS List the status of every Meraki device in the organization. .DESCRIPTION List the status of every Meraki device in the organization. Can be filtered by Network or Serial number. .PARAMETER Id The ID of the Network to retrieve device status from. .PARAMETER Serial Then serial number of the device to retrieve status from. .PARAMETER OrgId The organization Id to use. .PARAMETER ProfileName The named profile to use. .OUTPUTS An array of device status objects #> } function Get-MerakiOrganizationApplianceVpnStatuses() { [CmdletBinding(DefaultParameterSetName = 'default')] Param( [Parameter(ValueFromPipelineByPropertyName)] [Alias('NetworkId')] [string]$id, [ValidateScript({$_ -is [int]})] [ValidateSet(3,300)] [int]$PerPage, [ValidateScript({$_ -is [int]})] [int]$Pages = 1, [Parameter(ParameterSetName = 'org')] [string]$OrgId, [Parameter(ParameterSetName = 'profile')] [string]$ProfileName ) Begin { $Headers = Get-Headers if (-not $OrgID) { $config = Read-Config if ($profileName) { $OrgID = $config.profiles.$profileName if (-not $OrgID) { throw "Invalid profile name!" } } else { $OrgID = $config.profiles.default } } $NetworkIds = [List[String]]::New() if ($PerPage) { $Query = "?perPage={0}" -f $PerPage } $Uri = "{0}/organizations/{1}/appliance/vpn/statuses" -f $BaseURI, $OrgId } Process { if ($id) { $NetworkIds.Add($Id) } } End { $Results = [List[PsObject]]::New() if ($NetworkIds.Count -gt 0) { if ($Query) {$Query += "&"} else {$Query += "?"} $Query = "{0}networkIds[]={1}" -f $Query, ($NetworkIds.ToArray() -join ',') } try { $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 ($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.ToArray() } catch { throw $_ } } <# .SYNOPSIS List VPN status for networks in an organization .DESCRIPTION Show VPN status for networks in an organization. can be filtered by Networks. .PARAMETER id The network ID to get VPN status for. .PARAMETER PerPage The number of entries per page returned. Acceptable range is 3 - 300. Default is 300. .PARAMETER Pages The number of pages to return. Default is 1, 0 = return all pages. .PARAMETER OrgId The organization to use. .PARAMETER ProfileName The named profile to use. .OUTPUTS An array of VPN statuses. #> } function Get-MerakiOrganizationApplianceUplinkStatuses() { [CmdletBinding(DefaultParameterSetName = 'default')] Param( [Parameter( ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [String]$networkId="*", [Parameter( ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [String]$serial="*", [Parameter(ParameterSetName = 'org')] [string]$OrgId, [Parameter(ParameterSetName = 'profile')] [string]$ProfileName ) if (-not $OrgID) { $config = Read-Config if ($profileName) { $OrgID = $config.profiles.$profileName if (-not $OrgID) { throw "Invalid profile name!" } } else { $OrgID = $config.profiles.default } } else { $config = Read-Config } $Uri = "{0}/organizations/{1}/appliance/uplink/statuses" -f $BaseURI, $OrgID $Headers = Get-Headers try { $response = Invoke-RestMethod -Method GET -Uri $Uri -Headers $Headers -PreserveAuthorizationOnRedirect return $response | Where-Object {$_.networkID -like $networkID -and $_.serial -like $serial} } catch { throw $_ } <# .SYNOPSIS Returns the Uplink status of Meraki Networks. .PARAMETER networkId Filters the output by network Id. .PARAMETER serial Filters the output by Appliance serial number. Partial serial number can be specified bu using the * wildcard. i.e. "*HG4U" .OUTPUTS An array of Meraki uplink objects. #> } Set-Alias -Name GMAppUpStat -value Get-MerakiOrganizationApplianceUplinkStatuses -Option ReadOnly Set-Alias -Name Get-MerakiApplianceUplinkStatuses -value Get-MerakiOrganizationApplianceUplinkStatuses -Option ReadOnly function Get-MerakiOrganizationApplianceVpnStats() { [cmdletBinding(DefaultParameterSetName = 'default')] Param( [Parameter( Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [string]$id, [ValidateSet({$_ -is [int]})] [int]$perPage=100, [ValidateSet({$_ -is [int]})] [int]$TimeSpan=5, [switch]$Summarize, [Parameter(ParameterSetName = 'org')] [string]$OrgId, [Parameter(ParameterSetName = 'profile')] [string]$ProfileName ) Begin { if (-not $OrgID) { $config = Read-Config if ($profileName) { $OrgID = $config.profiles.$profileName if (-not $OrgID) { throw "Invalid profile name!" } } else { $OrgID = $config.profiles.default } } else { $config = Read-Config } $Headers = Get-Headers $config = read-config if ($profileName) { $OrgID = $config.profiles.$profileName if (-not $OrgId) { throw "Invalid profile name!" } } else { $OrgID = $config.profiles.default } class vpnPeer { [string]$networkID [string]$networkName [string]$peerNetworkId [string]$peerNetworkName [int]$receivedKilobytes [int]$sentKilobytes } class summaryVpnPeer { [string]$networkID [string]$networkName [int]$totalReceivedKilobytes [int]$totalSentKilobytes } } Process { $Network = Get-MerakiNetwork -networkID $id $Uri = "{0}/organizations/{1}/appliance/vpn/stats" -f $BaseURI, $OrgID $TimeSpan_Seconds = (New-TimeSpan -Days $timespan).TotalSeconds $Uri = "{0}?perPage={1}&networkIds%5B%5D={2}×pan={3}" -f $Uri, $timespan, $id, $TimeSpan_Seconds try { $response = Invoke-RestMethod -Method GET -Uri $Uri -Headers $Headers -PreserveAuthorizationOnRedirect $peers = $response.merakiVpnPeers $PeerNetworks = New-object System.Collections.Generic.List[psobject] foreach ($peer in $peers) { $P = [vpnPeer]::New() $P.networkID = $id $P.networkName = $Network.name $P.peerNetworkId = $peer.networkId $P.peerNetworkName = $peer.networkName $P.receivedKilobytes = $peer.usageSummary.receivedInKilobytes $P.sentKiloBytes = $peer.usageSummary.sentInKilobytes $PeerNetworks.Add($P) } $vpnPeers = $PeerNetworks.ToArray() if ($Summarize) { $summary = [summaryVpnPeer]::New() $summary.networkID = $id $Summary.networkName = $Network.name $summary.totalReceivedKilobytes = ($vpnPeers | Measure-Object -Property receivedKilobytes -Sum).Sum $summary.totalSentKilobytes = ($vpnPeers | Measure-Object -Property sentKilobytes -Sum).Sum return $summary } else { $vpnPeers } } catch { throw $_ } } <# .SYNOPSIS Returns VPN statistics for the given organization network. .PARAMETER id The Network Id. .PARAMETER perPage The number of entries per page returned. Acceptable range is 3 - 300. Default is 300. .PARAMETER timespan Number of seconds to return data for. default = 5. .PARAMETER Summarize Summarize the statistics, AN array op VPN peer objects or a summary object. .PARAMETER OrgId Optional Organization Id. .PARAMETER ProfileName Optional Profile Name #> } Set-Alias -Name GMAVpnStats -Value Get-MerakiOrganizationApplianceVpnStats -Option ReadOnly Set-Alias -Name GMOAVpnStats -Value Get-MerakiOrganizationApplianceVpnStats -Option ReadOnly set-Alias -Name Get-MerakiNetworkApplianceVpnStats -Value Get-MerakiOrganizationApplianceVpnStats function Merge-MerakiOrganizationNetworks() { [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'default')] Param( [Parameter(Mandatory = $true)] [string]$Name, [Parameter( Mandatory, ValueFromPipelineByPropertyName )] [Alias('NetworkId')] [string]$Id, [string]$EnrollmentString, [Parameter(ParameterSetName = 'org')] [string]$OrgId, [Parameter(ParameterSetName = 'profile')] [string]$ProfileName ) Begin { if (-not $OrgID) { $config = Read-Config if ($profileName) { $OrgID = $config.profiles.$profileName if (-not $OrgID) { throw "Invalid profile name!" } } else { $OrgID = $config.profiles.default } } else { $config = Read-Config } $Networks = [List[string]]::New() $Header = Get-Headers $Uri = "{0}/organizations/{1}/networks/combine" -f $BaseURI, $OrgID } Process { $Networks.Add($Id) } End { $_Body = @{ "name" = $Name "networkIds" = ($Networks.ToArray()) } if ($EnrollmentString) { $_Body.Add("enrollmentString", $EnrollmentString) } $body = $_Body | ConvertTo-Json -Compress if ($PSCmdlet.ShouldProcess('Merge',"Networks $($Networks -join ',')")) { try { $response = Invoke-RestMethod -Method POST -Uri $Uri -Headers $Header -Body $Body -PreserveAuthorizationOnRedirect return $response } catch { throw $_ } } } <# .SYNOPSIS Combine multiple Meraki networks into a single network. .PARAMETER Id The ID of the networks to combine. You should pass an array of network objects to combine those networks. .PARAMETER Name The name of the combined network. .PARAMETER NetworkIds A list of the network IDs that will be combined. If an ID of a combined network is included in this list, the other networks in the list will be grouped into that network. .PARAMETER EnrollmentString A unique identifier which can be used for device enrollment or easy access through the Meraki SM Registration page or the Self Service Portal. Please note that changing this field may cause existing bookmarks to break. All networks that are part of this combined network will have their enrollment string appended by '-network_type'. If left empty, all existing enrollment strings will be deleted. .PARAMETER OrgId Optional Organization Id .PARAMETER ProfileName Optional Profile name. .OUTPUTS A network object #> } Function New-MerakiSecretsVault() { [CmdletBinding()] Param ( [ValidateSet('Password', 'none')] [string]$Authentication, [ValidateSet('Prompt','none')] [string]$Interaction ) $CurrentConfig = Get-SecretStoreConfiguration | Where-Object {$_.Scope -eq "CurrentUser"} $Params = @{} if ($Authentication) { if ($Authentication -ne $CurrentConfig.Authentication) { $Params.Add("Authentication",$Authentication) } } if ($Interaction) { if ($Interaction -ne $CurrentConfig.Interaction) { $Params.Add("Interaction", $Interaction) } } if ($Params.Count -gt 0) { Set-SecretStoreConfiguration -Scope CurrentUser @Params } $Vaults = Get-SecretVault if ($Vaults) { if ($vaults.ModuleName -contains 'Microsoft.PowerShell.SecretStore') { Write-Host "There is currently an existing Vault register for module $($Vault.ModuleName)." -ForegroundColor Yellow Write-Host "The secretStore vault currently always operates in the logged user scope." -ForegroundColor Yellow Write-Host "Registering SecretStore multiple times with different names just results in duplication of teh same store," -ForegroundColor Yellow Write-Host "This vault will be used to store the secrets." -ForegroundColor Yellow } } else { Register-SecretVault -Name "LocalVault" -ModuleName Microsoft.PowerShell.SecretStore -DefaultVault -AllowClobber Write-Host "Vault 'LocalVault' created." -ForegroundColor Yellow } <# .DESCRIPTION Created a local Secret vault to store secrets. .PARAMETER Authentication Specifies how to authenticate access to the SecretStore. The value must be Password or None. If specified as None, the cmdlet enables access to the SecretStore without a password. The default authentication is Password. .PARAMETER Interaction Specifies whether the SecretStore should prompt a user when they access it. If the value is Prompt, the user is prompted for their password in interactive sessions when required. If the value is None, the user is not prompted for a password. If the value is None and a password is required, the cmdlet requiring the password throws a error. .NOTES Setting Authentication to 'none' is less secure than 'password'. Specifying 'none' may be useful for automated tasks where prompting for password is not practical. Authentication should always be set to 'password' for interactive sessions. If you set a password, you should set -Interaction to 'Prompt' otherwise an error will occur. Secret Store only uses the local user scope. Registering Secret Store multiple times only results in duplication of the same store, This module does not support vaults registered with a different module. Secrets will ALWAYS be stored in the default vault! #> } |