OK, so this went stale, so I'll give an answer in case anyone else lands here looking.
First off - no, there is no way to get this association directly.
As indicated in the question, NICs appear to be the solution.
I requested all of the NIC data back (across all subscriptions to ensure full coverage of all VNETS).
(https://docs.microsoft.com/en-us/rest/api/network/list-network-interface-cards-within-a-resource-group)'
Within this data, you can determine the parent vnet by reading the subnet data...
Here's a little test I put together in Python that seems to accomplish the task:
Data response from Azure (shows 2 NICS):
{
"value": [
{
"name": "REDACTED",
"id": "REDACTED",
"etag": "REDACTED",
"location": "eastus",
"type": "Microsoft.Network/networkInterfaces",
"properties": {
"provisioningState": "Succeeded",
"macAddress": "REDACTED",
"primary": true,
"virtualMachine": {
"id": "/subscriptions/REDACTED/resourceGroups/REDACTED/providers/Microsoft.Compute/virtualMachines/MyVM1"
},
"dnsSettings": {
"internalDomainNameSuffix": "REDACTED",
"dnsServers": [],
"appliedDnsServers": []
},
"enableIPForwarding": false,
"resourceGuid": "REDACTED",
"networkSecurityGroup": {
"id": "/subscriptions/REDACTED/resourceGroups/REDACTED/providers/Microsoft.Network/networkSecurityGroups/REDACTED"
},
"ipConfigurations": [
{
"etag": "W/\"REDACTED\"",
"id": "/subscriptions/REDACTED/resourceGroups/REDACTED/providers/Microsoft.Network/networkInterfaces/REDACTED/ipConfigurations/ipconfig1",
"name": "ipconfig1",
"properties": {
"subnet": {
"id": "/subscriptions/REDACTED/resourceGroups/REDACTED/providers/Microsoft.Network/virtualNetworks/MyVNET1/subnets/REDACTED"
},
"primary": true,
"privateIPAddressVersion": "IPv4",
"publicIPAddress": {
"id": "/subscriptions/REDACTED/resourceGroups/REDACTED/providers/Microsoft.Network/publicIPAddresses/REDACTED"
},
"privateIPAllocationMethod": "Dynamic",
"privateIPAddress": "10.0.0.4",
"provisioningState": "Succeeded"
}
}
],
"enableAcceleratedNetworking": false
}
},
{
"name": "REDACTED",
"id": "REDACTED",
"etag": "REDACTED",
"location": "eastus",
"type": "Microsoft.Network/networkInterfaces",
"properties": {
"provisioningState": "Succeeded",
"macAddress": "REDACTED",
"primary": true,
"virtualMachine": {
"id": "/subscriptions/REDACTED/resourceGroups/REDACTED/providers/Microsoft.Compute/virtualMachines/MyVM2"
},
"dnsSettings": {
"internalDomainNameSuffix": "REDACTED",
"dnsServers": [],
"appliedDnsServers": []
},
"enableIPForwarding": false,
"resourceGuid": "REDACTED",
"networkSecurityGroup": {
"id": "/subscriptions/REDACTED/resourceGroups/REDACTED/providers/Microsoft.Network/networkSecurityGroups/REDACTED"
},
"ipConfigurations": [
{
"etag": "W/\"REDACTED\"",
"id": "/subscriptions/REDACTED/resourceGroups/REDACTED/providers/Microsoft.Network/networkInterfaces/REDACTED/ipConfigurations/ipconfig1",
"name": "ipconfig1",
"properties": {
"subnet": {
"id": "/subscriptions/REDACTED/resourceGroups/REDACTED/providers/Microsoft.Network/virtualNetworks/MyVNET1/subnets/REDACTED"
},
"primary": true,
"privateIPAddressVersion": "IPv4",
"publicIPAddress": {
"id": "/subscriptions/REDACTED/resourceGroups/REDACTED/providers/Microsoft.Network/publicIPAddresses/REDACTED"
},
"privateIPAllocationMethod": "Dynamic",
"privateIPAddress": "10.0.0.4",
"provisioningState": "Succeeded"
}
}
],
"enableAcceleratedNetworking": false
}
}
}
]
}
Processing the data (note there are indentation issues in the output below):
def build_vms_by_net(data):
data = [data]
returnData = []
for i,obj in enumerate(data):
if 'value' in obj:
inner = obj['value']
for j, obj2 in enumerate(inner):
config = obj2['properties']['ipConfigurations']
for k, obj3 in enumerate(config):
if '/virtualNetworks/' in obj3['properties']['subnet']['id']:
vnetvals = obj3['properties']['subnet']['id'].split('/')
vnet = vnetvals[vnetvals.index('virtualNetworks')+1]
else:
vnet = ''
try:
vmvals = obj2['properties']['virtualMachine']['id'].split('/')
vm = vmvals[-1]
subscriptionId = vmvals[vmvals.index('subscriptions')+1]
resourceGroupName = vmvals[vmvals.index('resourceGroups')+1]
if (vnet != '' and vm != ''):
returnData.append({
"nicId" : obj2['name'],
"vm" : vm,
"vnet" : vnet,
"subscriptionId" : subscriptionId,
"resourceGroupName" : resourceGroupName
})
except Exception:
# Azure has a habit of orphaning resources, in the event that
# a nic is not associated with a virtual machine, we just don't
# append its info and continue w/ the rest of the processing
pass
# as it likely means the VM was deleted, but not the NIC
vnet = ''
vm = ''
return returnData
The outpur is as follows: [
{
"subscriptionId": "REDACTED",
"vnet": "REDACTED",
"nicId": "REDACTED",
"vm": "REDACTED",
"resourceGroupName": "REDACTED"
},
{
"subscriptionId": "REDACTED",
"vnet": "REDACTED",
"nicId": "REDACTED",
"vm": "REDACTED",
"resourceGroupName": "REDACTED"
}
]
Looping over all of your resource groups, you'll build a complete list, which you can then process further to make a list of VMs by VNET if you so chose.
Note that you may need to beef up the error handling, but this test is plenty to get started...it can also be the basis of making connections between a lot of other resources within Azure that have "Resource Group" as their parent (another, simpler example is get a Gateway's parent VNet from it's data response).