About Motifworks

At Motifworks, we are AZURESMART. We are one of the fastest-growing cloud solutions providers, specializing in Cloud Adoption, Application Innovation, and Effective Data Strategies. Our passion is to empower you to accelerate your digital transformation initiatives using the Microsoft Azure cloud. We’re here to simplify your path to explore what’s possible.

Corporate Office

200 W Townsontown Blvd, Suite 300D, Towson, MD, 21204, US

Regional Offices

Philadelphia ¦ Detroit ¦ Dallas ¦ Florida ¦ Cincinnati ¦ Ohio

Development Office

101 Specialty Business Center, Balewadi Road, Pune- 411045, Maharashtra, India

Connect with us
info@motifworks.com +1 443-424-2340

Motifworks Can Help Integrate Azure Virtual WAN With Your Environment

Motifworks Can Help Integrate Azure Virtual WAN With Your Environment


Motifworks is pleased to announce its support for automating the provisioning of Azure Virtual WAN with Cisco Meraki appliances. Motifworks is a cloud solution provider, specializing in Cloud Adoption, Application Innovation, and Effective Data Strategies.

Several customers that I have worked with have asked if Meraki was compatible or a supported solution with Azure Virtual WAN. While Meraki is technically not a supported offering for automation, with Virtual WAN it can be implemented and deployed rapidly at scale. Hundreds of Meraki sites can be added in minutes using this automation.


Why do customers want to integrate Cisco Meraki and Azure Virtual WAN?

Customers love the simplicity and robust features of Cisco Meraki such as Auto-VPN, SD-WAN, Traffic Shaping, built-in Wireless Access Points, and Open APIs, advanced analytics, among others. To learn more about the benefits and features of Cisco Meraki, check out the Cisco Meraki Appliances.

Azure Virtual WAN provides optimized and automated branch connectivity reducing the complexity associated with managing a hub-and-spoke architecture. Virtual WAN allows customers to take advantage of Site-to-Site, Point-to-Site, and ExpressRoute circuits from a single interface. The other major bonus of Azure Virtual WAN is that your branches or on-premise sites can leverage the Azure backbone to access services in other regions. For a more in-depth understanding of Azure Virtual WAN, visit the Microsoft Azure document What is Azure Virtual WAN?


How does the automation between Azure Virtual WAN work?

By leveraging open API’s and PowerShell Cmdlets from both Microsoft and Cisco, the creation of Azure resources and Meraki sites can be configured rapidly. Below is an outline of the process.

  • Create the Azure Virtual WAN
    • Create the Virtual HUB
    • Peer the Virtual Hub with a Virtual Network
    • Create the VPN Gateway
    • Create the VPN Site
    • Create the VPN Connection
  • Configure the Cisco Meraki Networks(s)
    • Configure the Meraki Network with Third Party VPN Peers

Sample Code is given below for configuring a single Azure Virtual WAN with a Cisco Meraki Site.

#region Create VPN Sites and VPN Connections
    $pre_Shared_Key = (New-RandomPassword -InputStrings abcdefghijklmnopqrstuvwxyz, ABCDEFGHIJKLMNOPQRSTUVWXYZ, 1234567890 -PasswordLength 64)
    Get-MerakiOrganization | Out-Null
    Get-MerakiOrganizationInventory -meraki_Organization_ID $obj_Meraki_Organization -meraki_URL_Prefix $GLOBAL:str_Meraki_Url_Prefix -meraki_Headers $obj_Meraki_Headers | `
            ForEach-Object {
            $meraki_Network_Name = (Get-MerakiNetworks -meraki_Organization_ID $obj_Meraki_Organization -meraki_URL_Prefix $GLOBAL:str_Meraki_Url_Prefix -meraki_Headers $obj_Meraki_Headers -meraki_network_ID $_.networkID).Name
            $meraki_Network_Name = $meraki_Network_Name.Replace(' ', '')
            #region Create VPN Site
                Try {
                    New-AzVpnSite -ResourceGroupName $az_Resource_Group_Name `
                        -Name "vpnSite-$($meraki_Network_Name)" `
                        -Location $az_Location `
                        -VirtualWanName $obj_Virtual_WAN.Name `
                        -VirtualWanResourceGroupName $obj_Virtual_WAN.ResourceGroupName `
                        -IpAddress $_.publicIP `
                        -AddressSpace $vpnSiteAddressSpaces `
                        -DeviceModel $_.model `
                        -DeviceVendor 'Cisco Meraki' `
                        -AsJob `
                        -ErrorAction Stop
                    Do {
                        If (Get-AzVpnSite -ResourceGroupName $az_Resource_Group_Name -Name "vpnSite-$($meraki_Network_Name)" -ErrorAction SilentlyContinue) { Break }
                        Write-Verbose "Waiting for the VPN Site vpnSite-$($meraki_Network_Name) to be Created..."
                        Start-Sleep -Seconds 5
                    } Until ((Get-Job | Where-Object { $_.Command -eq 'New-AzVpnSite' }).State -eq 'Completed')
                    (Get-Job | Where-Object { $_.Command -eq 'New-AzVpnSite' }).State -eq 'Completed' | ForEach-Object { Remove-Job -State 'Completed' }
                } Catch {
                    Write-Error $_
                } Finally {
                    $obj_VPN_Site = (Get-AzVpnSite -ResourceGroupName $az_Resource_Group_Name -Name "vpnSite-$($meraki_Network_Name)")
        #region Create VPN Connections
        Try {
            New-AzVpnConnection -Name "connection-$($meraki_Network_Name)" `
                -ResourceGroupName $az_Resource_Group_Name `
                -ParentResourceName $obj_Virtual_WAN_Gateway.Name `
                -VpnSite $obj_VPN_Site `
                -SharedKey (ConvertTo-SecureString -String $pre_Shared_Key -AsPlainText -Force) `
                -VpnConnectionProtocolType IKEv1 `
                -AsJob `
                -ErrorAction Stop
            Do {
                If (Get-AzVpnConnection -Name "connection-$($meraki_Network_Name)" -ParentResourceName $obj_Virtual_WAN_Gateway.Name -ResourceGroupName $az_Resource_Group_Name -ErrorAction SilentlyContinue) { Break }
                Out-Host -InputObject "Waiting on Connection connection-$($meraki_Network_Name) to be Associated with the VWAN HUB....." -Verbose
                Start-Sleep -Seconds 5
            } Until ((Get-Job | Where-Object { $_.Command -eq 'New-AzConnection' }).State -eq 'Completed')
            (Get-Job | Where-Object { $_.Command -eq 'New-AzConnection' }).State -eq 'Completed' | ForEach-Object { Remove-Job -State 'Completed' }
        } Catch {
            Write-Error $_
        } Finally {
            $obj_VPN_Connection = (Get-AzVpnConnection -Name "connection-$($meraki_Network_Name)" -ParentResourceName $obj_Virtual_WAN_Gateway.Name -ResourceGroupName $az_Resource_Group_Name)
            #region Download Azure Virtual WAN VPN Configuration
            If($configGenerated -eq $false) {
                $az_Storage_Account_Name = "config$(Get-Date -format 'MMddyyy')"
                If ($null -eq (Get-AzResourceGroup -Name $az_Storage_Account_Resource_Group_Name -Location $az_Location)) {
                    New-AzResourceGroup -Name $az_Storage_Account_Resource_Group_Name -Location $location
                If ($null -eq (Get-AzStorageAccount -ResourceGroupName $az_Storage_Account_Resource_Group_Name -Name $az_Storage_Account_Name)) {
                    $storageAccount = New-AzStorageAccount `
                        -Name $az_Storage_Account_Name `
                        -ResourceGroupName $az_Storage_Account_Resource_Group_Name `
                        -Location $az_Location `
                        -SkuName Standard_LRS `
                        -Kind BlobStorage `
                        -AccessTier Cool `
                        -EnableHttpsTrafficOnly $true `
                } Else {
                    $storageAccount = Get-AzStorageAccount -ResourceGroupName $az_Storage_Account_Resource_Group_Name -Name $az_Storage_Account_Name
                $saKey = (Get-AzStorageAccountKey -ResourceGroupName $storageAccount.ResourceGroupName -Name $storageAccount.StorageAccountName)[1].Value
                $ctx = New-AzStorageContext -StorageAccountName $storageAccount.StorageAccountName -StorageAccountKey $saKey
                Try {
                    $container = Get-AzStorageContainer -Name 'vpnsiteconfig' -Context $ctx -ErrorAction Stop
                } Catch {
                    New-AzStorageContainer -Name 'vpnsiteconfig' -Context $ctx
                } Finally { 
                    $container = Get-AzStorageContainer -Name 'vpnsiteconfig' -Context $ctx 
                $sasToken = New-AzStorageAccountSASToken -Service Blob -ResourceType Service, Container, Object -Permission racwdlup -Context $ctx -ExpiryTime (Get-Date).AddDays(+1)   
                Do {
                    Write-Host "Waiting on the VPN Configuration to be Generated and Downloaded...." -Verbose        
                    $sasUrl = Get-AzVirtualWanVpnConfiguration -VirtualWan $obj_Virtual_WAN -StorageSasUrl "$($container.CloudBlobContainer.Uri.AbsoluteUri)/$($az_Storage_Account_Name)$($sasToken)" -VpnSite $obj_VPN_Site
                    $vpnConfigData = (Invoke-RestMethod -Method Get -Uri $sasUrl.SasUrl)
                } Until ($vpnConfigData)
                If($vpnConfigData) { $configGenerated = $true }
                Write-Host "VPN Configuration Downloaded..." -Verbose
                #region Build JSON Body to Create the Meraki Third Party VPN Peers
                    $GLOBAL:json_body = @{
                        peers = @(
                            name = "Azure vWAN Gateway 1"
                            publicIp = "$($vpnConfigData.VpnSiteConnections.gatewayConfiguration.IpAddresses.Instance0)"
                            privateSubnets = @("$($vpnConfigData.vpnSiteConnections.hubConfiguration.AddressSpace)", "$($vpnConfigData.vpnSiteConnections.hubConfiguration.ConnectedSubnets)")
                            ipsecPoliciesPreset = "azure"
                            secret = "$($vpnConfigData.VpnSiteConnections.connectionConfiguration.PSK)"
                            name = "Azure vWAN Gateway 2"
                            publicIp = "$($vpnConfigData.VpnSiteConnections.gatewayConfiguration.IpAddresses.Instance1)"
                            privateSubnets = @("$($vpnConfigData.vpnSiteConnections.hubConfiguration.AddressSpace)", "$($vpnConfigData.vpnSiteConnections.hubConfiguration.ConnectedSubnets)")
                            ipsecPoliciesPreset = "azure"
                            secret = "$($vpnConfigData.VpnSiteConnections.connectionConfiguration.PSK)"
                #region Update Meraki Third Party VPN Peers
                    Try {
                        Invoke-RestMethod -Method PUT -Uri "$($GLOBAL:str_Meraki_Url_Prefix)/organizations/$($GLOBAL:obj_Meraki_Organization)/thirdPartyVPNPeers" -Headers $GLOBAL:obj_Meraki_Headers -Body ($GLOBAL:json_body | ConvertTo-Json -Depth 10) -ErrorVariable respError -ErrorAction Stop
                    } Catch { Write-Error $_}

The snippet above is part of the proof of concept PowerShell script within my GitHub Repo. For the entire script please visit my GitHub Repo https://github.com/jsmallwood/az-vwan-meraki-integration.

We are very pleased to see the automation provided by Motifworks connecting Meraki devices to Azure Virtual WAN using Microsoft and Meraki open APIs.

-Reshmi Yandapalli, Principal PM, Azure Networking at Microsoft Corp.

To see customers and partners using our Cisco Meraki dashboard APIs to integrate the simplicity of our platform with Microsoft’s cloud is the type of outcome we hope for.

-Mitchell Gulledge, Sr Technical Marketing Engineer, Cisco Meraki


To learn more on how Motifworks can help integrate Azure Virtual WAN with your environment, write to us at info@motifworks.com

For more on PowerShell and the Cisco Meraki APIs, follow the links below:

Az PowerShell Network Module Reference

Cisco Meraki Developer Hub | Rest API | Getting Started


Author Bio:

Jason Smallwood

AzureSmart Architect – Motifworks

Jason works closely with clients to build solutions around their business requirements, which ranges from analyzing the landscape of their current environment to move to the cloud and improving their cloud deployments.