Trying to execute a POST script in PS, below is the function. When I run the script I'm receiving a 308 error.
#Create a new dashboard administrator
function Post-MerakiAdmin {
param (
[string]$organizationId,
[string][Parameter(Mandatory=$true)]$name,
[string][Parameter(Mandatory=$true)]$email,
[string][Parameter(Mandatory=$true)]$access,
[string]$tags = ""
)
if (!$organizationId){
$orgIds = Get-MerakiOrganizations
$i = 0
ForEach($orgId in $orgIds){
Write-Host "$i $($orgId.name)"
$i++
}
Write-Host "`n"
While (!$organizationId){
$choice = Read-Host -Prompt "Select Organization"
$organizationId = $orgIds[$choice].id
}
}
$params = @{
name = $name
email = $email
orgAccess = $access
tags = $tags
}
$json = $params | ConvertTo-Json
$request = Invoke-RestMethod -Uri "$($api)/organizations/$($organizationId)/admins" -Method POST -Headers $header -Body $json -ContentType $contentType
return $request
}
Any assistance is appreciated.
What's the value of the $api variable? My guess would be that you've got a malformed URI. The 308 errors I've gotten in the past have been related to using the wrong URL. The API docs say to use the prefix: https://api.meraki.com/api/v0 before the /organizations/... bit. You could always just 'Write-Host $uri' before you call Invoke-RestMethod to see what it's sending.
Finally figured out the issue. On POST commands I had to change the URL, after reviewing multiple sites on the issue. The $api variable equaled https://api.meraki.com........, and what I had to do was create a 2nd variable ($postapi) and point it to the redirected URL. Once that was done script ran without error.
Hi,
Can you please give me an example? I'm having the exact same error on any Meraki POST call using PowerShell.
Here is an example Powershell script showing how to manage the 308. I don't remember if everything in here is working or not, but it should give you some ideas of things to do.
Disclaimer in advance: Powershell is not my forte, so apologies for the questionable quality of the code.
[CmdletBinding()] Param ( [string] $name, [string] $organization, [string] $network, [string] $device, [string] $port, [string] $psk, [switch] $getorgs = $false, [switch] $getnets = $false, [switch] $getdevs = $false, [switch] $gethealth = $false, [string] $newnet = $false, [string] $newdev = $false, [string] $newwhd = $false, [string] $setwhalert = $false, [string] $setssid = $false, [switch] $setssiddns = $false, [string] $setporttag = $false, [string] $setportvlan = $false, [string] $timespan = $false, [switch] $help = $false ) New-Variable -Scope global -Name headers $global:headers = @{ "Content-Type" = "application/json" "Accept" = "application/json" "X-Cisco-Meraki-API-Key" = "1234567890abcdefghijklmnopqrstuvwxyz1234" } New-Variable -Scope global -Name base_url $global:base_url = "https://api.meraki.com/api/v0" add-type @" using System.Net; using System.Security.Cryptography.X509Certificates; public class TrustAllCertsPolicy : ICertificatePolicy { public bool CheckValidationResult( ServicePoint srvPoint, X509Certificate certificate, WebRequest request, int certificateProblem) { return true; } } "@ $AllProtocols = [System.Net.SecurityProtocolType]'Ssl3,Tls,Tls11,Tls12' [System.Net.ServicePointManager]::SecurityProtocol = $AllProtocols [System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy Function Convert-FromUnixDate ($UnixDate) { [timezone]::CurrentTimeZone.ToLocalTime(([datetime]'1/1/1970').AddSeconds($UnixDate)) } function callGETService { $uri = $global:base_url + $args[0]; Write-Host 'Calling URI (GET):' $args[0]; try { $response = Invoke-RestMethod -Method GET -Uri $uri -Headers $global:headers } catch { $ret = @{ "statuscode" = $_.Exception.Response.StatusCode.value__ "statusmsg" = $_.Exception.Response.StatusDescription } $response = $ret | ConvertTo-Json } return $response } function callPOSTService { $json = $args[1] | ConvertTo-Json Write-Host 'Calling URI (POST): ' $args[0]; try { $response = Invoke-RestMethod -Method POST -Uri $uri -Body $json -Headers $global:headers } catch { $ret = @{ "statuscode" = $_.Exception.Response.StatusCode.value__ "statusmsg" = $_.Exception.Response.StatusDescription } $response = $ret } return $response } function callPUTService { $json = $args[1] | ConvertTo-Json Write-Host 'Calling URI (PUT): ' $args[0]; try { $response = Invoke-RestMethod -Method PUT -Uri $uri -Body $json -Headers $global:headers } catch { $ret = @{ "statuscode" = $_.Exception.Response.StatusCode.value__ "statusmsg" = $_.Exception.Response.StatusDescription } $response = $ret } return $response } function find308dest { # Powershell really seems to be unfriendly with 308/Permanent Redirect given by # Dashboard when making POST/PUT requests. Prior to issuing a POST/PUT, send the # URL through this function, which will make a GET to determine the proper host # to send the API request to. $fix_uri = $args[0] + $args[1]; $fix_res = Invoke-WebRequest -Method GET -Uri $fix_uri -Headers $global:headers -MaximumRedirection 0 -ErrorAction SilentlyContinue $fix_url = $fix_res.Headers.Location $uri = $fix_url -replace $args[1],"" return $uri } function getHelp { Write-Host "powershell -file mcmd.ps1 -action [name] [options]" Write-Host "" Write-Host "ACTION" Write-Host "" Write-Host " -getorgs" Write-Host " Returns a list of Organizations that the provided API Key has access to." Write-Host "" Write-Host " -getnets" Write-Host " Returns a list of Networks configured in the provided Organization." Write-Host " Used together with -organization" Write-Host "" Write-Host " -getdevs" Write-Host " Returns a list of Devices configured in the provided Network." Write-Host " Used together with -network" Write-Host "" Write-Host " -newnet ""network name""" Write-Host " Create a new network in a given Organization." Write-Host " Used together with -organization" Write-Host "" Write-Host " -newdev serial-number" Write-Host " Claim a device into a given Network." Write-Host " Used together with -network" Write-Host "" Write-Host " -newwhd destination-url" Write-Host " Creates a new Webhook Destination; Returns the Webhook ID." Write-Host " Used together with -network" Write-Host "" Write-Host " -setwhalert webhook-id" Write-Host " Enable Configuration Change Alerts for a Webhook with the provided ID." Write-Host " Used together with -network" Write-Host "" Write-Host " -setssid ""ssid name""" Write-Host " Configures a SSID for PSK with a provided Pre-Shared Key." Write-Host " Used together with -network and -psk" Write-Host "" Write-Host " -setssiddns" Write-Host " Configures the Layer 3 Firewall for the first SSID to allow only Umbrella DNS." Write-Host " Used together with -network" Write-Host "" Write-Host " -setporttag ""tag-name""" Write-Host " Sets a tag on a specfied switch port." Write-Host " Used together with -network and -port" Write-Host "" Write-Host " -setportvlan ""vlan-id""" Write-Host " Sets a VLAN on a specfied switch port." Write-Host " Used together with -network and -port" Write-Host "" Write-Host "OPTIONS" Write-Host "" Write-Host " -organization" Write-Host " Specify the Organization to apply the action to." Write-Host "" Write-Host " -network" Write-Host " Specify the Network to apply the action to." Write-Host "" Write-Host " -device" Write-Host " Specify the Device to apply the action to." Write-Host "" Write-Host " -psk" Write-Host " Specify the PSK to use when configuring a SSID." Write-Host "" Write-Host " -port" Write-Host " Specify the Port to use when configuring a switch port." Write-Host "" } # get all organizations function getOrgs { $res = callGETService '/organizations' Foreach ($x in $res) { Write-Host "* $($x.id) - $($x.name)" } } # get all networks function getNets { $uri = '/organizations/' + $args[0] + '/networks'; $res = callGETService $uri; Foreach ($x in $res) { Write-Host "* $($x.id) - $($x.name)" } } # get all devices function getDevs { $uri = '/networks/' + $args[0] + '/devices' $res = callGETService $uri Foreach ($x in $res) { Write-Host "* $($x.serial) - $($x.model) - $($x.mac)" } } # add network function newNet { $network = @{ name=$args[0] timeZone='Etc/GMT' type='switch appliance wireless' } $testuri = "/organizations" $newhost = find308dest $global:base_url $testuri $uri = $newhost + '/organizations/' + $args[1] + '/networks' $res = callPOSTService $uri $network $id = $res.id $sc = $res.statuscode if ($sc -eq $null) { Write-Host 'New network' '"'$id'"' 'created.' } else { $rout = $res | ConvertTo-Json Write-Host "" Write-Host $rout } } # add device function newDev { $device = @{ serial=$args[0] } $testuri = "/networks/" + $args[1] $newhost = find308dest $global:base_url $testuri $uri = $newhost + '/networks/' + $args[1] + '/devices/claim' $res = callPOSTService $uri $device $id = $res.id $sc = $res.statuscode if ($sc -eq $null) { Write-Host 'New device' '"'$args[1]'"' 'claimed.' } else { $rout = $res | ConvertTo-Json Write-Host "" Write-Host $rout } } # add webhook destination function newWebhookDest { $wh = @{ name='Webhook' sharedSecret='' url=$args[0] } $testuri = "/networks/" + $args[1] $newhost = find308dest $global:base_url $testuri $uri = $newhost + '/networks/' + $args[1] + '/httpServers' $res = callPOSTService $uri $wh $id = $res.id $sc = $res.statuscode if ($sc -eq $null) { Write-Host 'New webhook destination' '"'$id'"' 'created.' } else { $rout = $res | ConvertTo-Json Write-Host "" Write-Host $rout } } # set webhook alert for config changes function setWebhook { $wh = @{ defaultDestinations=@{ httpServerIds=@($args[0]) } alerts=@(@{ type='settingsChanged' enabled='true' }) } $testuri = "/networks/" + $args[1] $newhost = find308dest $global:base_url $testuri $uri = $newhost + "/networks/" + $args[1] + "/alertSettings" $res = callPUTService $uri $wh $id = $res.number $sc = $res.statuscode if ($sc -eq $null) { Write-Host "Configuration update alerts enabled for webhook" '"'$args[0]'"' "." } else { $rout = $res | ConvertTo-Json Write-Host "" Write-Host $rout } } # configure SSID function setSsid { $ssid = @{ name=$args[0] enabled='true' authMode='psk' encryptionMode='wpa' wpaEncryptionMode='WPA2 only' psk=$args[2] } $testuri = "/networks/" + $args[1] $newhost = find308dest $global:base_url $testuri $uri = $newhost + "/networks/" + $args[1] + "/ssids/0" $res = callPUTService $uri $ssid $id = $res.number $sc = $res.statuscode if ($sc -eq $null) { Write-Host "SSID" '"'$id'"' " configured." } else { $rout = $res | ConvertTo-Json Write-Host "" Write-Host $rout } } # configure SSID L3 FW to only allow Umbrella DNS function setSsidDns { $ssid = @{ rules=@( @{ comment='Allow Umbrella DNS' policy='allow' protocol='udp' destPort='53' destCidr='208.67.222.222/32' }, @{ comment='Allow Umbrella DNS' policy='allow' protocol='udp' destPort='53' destCidr='208.67.220.220/32' }, @{ comment='Block Other DNS' policy='deny' protocol='udp' destPort='53' destCidr='any' } ) } $testuri = "/networks/" + $args[0] $newhost = find308dest $global:base_url $testuri $uri = $newhost + "/networks/" + $args[0] + "/ssids/0/l3FirewallRules" $res = callPUTService $uri $ssid $id = $res.number $sc = $res.statuscode if ($sc -eq $null) { Write-Host "DNS Restrictions set on SSID." } else { $rout = $res | ConvertTo-Json Write-Host "" Write-Host $rout } } # configure tag on switch port function setPortTag { $port = @{ tags=$args[0] } $testuri = "/devices/" + $args[1] + "/switchPorts" $newhost = find308dest $global:base_url $testuri $uri = $newhost + "/devices/" + $args[1] + "/switchPorts/" + $args[2] $res = callPUTService $uri $port $id = $res.number $sc = $res.statuscode if ($sc -eq $null) { Write-Host "Tag" '"'$args[0]'"' "added to port" '"'$args[2]'"' "." } else { $rout = $res | ConvertTo-Json Write-Host "" Write-Host $rout } } # configure vlan on switch port function setPortVlan { $port = @{ type='access' voiceVlan='' vlan=$args[0] } $testuri = "/devices/" + $args[1] + "/switchPorts" $newhost = find308dest $global:base_url $testuri $uri = $newhost + "/devices/" + $args[1] + "/switchPorts/" + $args[2] $res = callPUTService $uri $port $id = $res.number $sc = $res.statuscode if ($sc -eq $null) { Write-Host "Port" '"'$args[2]'"' "set as Access VLAN" '"'$args[0]'"' "." } else { $rout = $res | ConvertTo-Json Write-Host "" Write-Host $rout } } # get wireless health function getWirelessHealth { $tsend = [int64](([datetime]::UtcNow)-(get-date "1/1/1970")).TotalSeconds $offset = 3600 * $args[1] $tsstr = $tsend - $offset $uri = '/networks/' + $args[0] + '/connectionStats?t0=' + $tsstr + '&t1=' + $tsend $res = callGETService $uri; $total = $res.assoc + $res.auth + $res.dhcp + $res.dns + $res.success $ftotal = $res.assoc + $res.auth + $res.dhcp + $res.dns Write-Host "Wireless Health Information" Write-Host "(Last $($args[1]) Hours)" Write-Host "---------------------------" Write-Host " Total Connections: $($total)" Write-Host " Association Failures: $($res.assoc)" Write-Host "Authorization Failures: $($res.auth)" Write-Host " DHCP Failures: $($res.dhcp)" Write-Host " DNS Failures: $($res.dns)" Write-Host " ------" Write-Host " Failed Connections: $($ftotal)" Write-Host "Successful Connections: $($res.success)" Write-Host "" $uri = '/networks/' + $args[0] + '/failedConnections?t0=' + $tsstr + '&t1=' + $tsend $res = callGETService $uri; $count = 0 Write-Host "Last 10 Failures" Write-Host "---------------------------" Foreach ($x in $res) { $curtime = Convert-FromUnixDate $x.ts Write-Host "* Client $($x.clientMac) failed at $($curtime). Cause: $($x.type) ($($x.failureStep))" $count++ if ($count -eq 10) { break } } } If ($help -eq $true) { getHelp } ElseIf ($getorgs -eq $true) { getOrgs } ElseIf ($getnets -eq $true) { getNets $organization } ElseIf ($getdevs -eq $true) { getDevs $network } ElseIf ($gethealth -eq $true) { getWirelessHealth $network $timespan } ElseIf ($newnet -ne $false) { newNet $newnet $organization } ElseIf ($newdev -ne $false) { newDev $newdev $network } ElseIf ($newwhd -ne $false) { newWebhookDest $newwhd $network } ElseIf ($setwhalert -ne $false) { setWebhook $setwhalert $network } ElseIf ($setssid -ne $false) { setSsid $setssid $network $psk } ElseIf ($setssiddns -eq $true) { setSsidDns $network } ElseIf ($setporttag -ne $false) { setPortTag $setporttag $device $port } ElseIf ($setportvlan -ne $false) { setPortVlan $setportvlan $device $port } Else { getHelp }
here is my method of dealing with redirects on PUT or POST calls. Incase of assistance.
$resource = "https://api.meraki.com/api/v0/networks/$networkId/devices/$serial"
$redirect = Invoke-WebRequest -Uri $resource -MaximumRedirection 0 -Method PUT -Header $header -Body $body
if ($redirect.StatusCode -eq 308)
{
$resourceRedirect = $redirect.Headers.Location
Invoke-WebRequest -Uri $resourceRedirect -MaximumRedirection 0 -Method PUT -Header $header -Body $body
}
Thank you both @joshand and @MarkD_BT
I read somewhere else that instead of using the base URL https://api.meraki.com/api/v0, just use the redirect link you see when you work on your dashboard and follows the pattern: 'https://nXXX.meraki.com/api/v0'
However what you explained is so much cleaner.
Hey @LauraZ
glad it helped if your working against 1 organisation it's fine replacing the url, but If you have multiple orgs then this causes more issues. So I find it easier to use the base url and script a redirect so you can handle all orgs you might look after. Good luck with your scripting.
https://github.com/immolate/MerakiAPI
This is my self designed code... And though fairly messy. All the functions work.
You may have some answers to help you there. your $request = statement looks.. I can't decipher what it's doing. But maybe mine will help you?