-
-
Save marckean/0d35d11b112f44313a440df10d1ff152 to your computer and use it in GitHub Desktop.
# -------------------------------------------------------------------------------------------------------------- | |
# | |
# Title : Azure_VMs_Inventory_V1.0 | |
# Programmed by : Mark Ayre & Marc Kean | |
# Date : December, 2017 | |
# | |
# -------------------------------------------------------------------------------------------------------------- | |
# | |
# Script to inventory the VM's (both ASM and ARM based) against all Azure subscriptions the account has access to. | |
# Adapted from Azure Inventory.ps1 by Marc Kean: | |
# | |
# Associated URL: https://marckean.com/2016/06/24/azure-inventory-powershell/ | |
# GitHub: https://gist.github.com/marckean/0d35d11b112f44313a440df10d1ff152#file-azure-inventory-ps1 | |
# | |
# Written with Azure PowerShell Module v5.0.1 | |
# https://github.com/Azure/azure-powershell/releases/tag/v5.0.1-November2017 | |
# | |
# -------------------------------------------------------------------------------------------------------------- | |
########################################################################################## | |
################## Optional AAD SP Info for un-attended sign-in ################## | |
########################################################################################## | |
# SP = Service Principal | |
$SP_Password = '' # or Certificate Thumbprint | |
$AADAppId = '1xxxa2ce-7xxb-xxx8-ad21-1f22d65f8c07' | |
$TenantID = 'a8c74611-6xx1-4xxx-a439-689xxx016e87' # Directory ID | |
# Retrieve Azure Module properties | |
"" | |
"Validating installed PowerShell Version and Azure PowerShell Module version..." | |
$ReqVersions = Get-Module Azure -list | Select-Object Version, PowerShellVersion | |
# Current PowerShell version must be higher then the one required by the Azure Module | |
if($PSVersionTable.PSVersion.Major -lt $ReqVersions.PowerShellVersion.Major) | |
{ | |
$PSVerReq = $ReqVersions.PowerShellVersion | |
$PSVerInst = $PSVersionTable.PSVersion | |
"Validation failed..." | |
"Installed PowerShell version: $PSVerInst" | |
"Powershell version $PSVerReq required. Please update the version of Powershell on this system" | |
"Exiting Script" | |
Break | |
} | |
# Current script was tested with Azure module 5.0.1 | |
if($ReqVersions.Version.Major -lt 5) | |
{ | |
$AZModuleInst = $ReqVersions.Version | |
"Validation failed..." | |
"Installed Azure PS Module: $AZModuleInst. This script was tested with version 5.0.1" | |
"Please download and install/update the Azure Powershell module using the Microsoft Web Platform Installer..." | |
"Download link: https://github.com/Azure/azure-powershell/releases/tag/v5.0.1-November2017" | |
"Exiting Script" | |
Break | |
} | |
########################################################################################## | |
################################# Logon to Azure ################################## | |
########################################################################################## | |
switch -Wildcard ($SP_Password) | |
{ | |
?* { | |
$secpasswd = ConvertTo-SecureString $SP_Password -AsPlainText -Force | |
$Cred = New-Object System.Management.Automation.PSCredential ($AADAppId, $secpasswd) | |
Login-AzureRmAccount -Credential $cred -ServicePrincipal -TenantId $TenantId | |
} | |
default { | |
Login-AzureRmAccount | |
} | |
} | |
########################################################################################## | |
################################### Functions ##################################### | |
########################################################################################## | |
# Using logged in credentials | |
Function RestAPI-AuthToken ($TenantId) { | |
# Load ADAL Azure AD Authentication Library Assemblies | |
$adal = "${env:ProgramFiles(x86)}\Microsoft SDKs\Azure\PowerShell\ServiceManagement\Azure\Services\Microsoft.IdentityModel.Clients.ActiveDirectory.dll" | |
$adalforms = "${env:ProgramFiles(x86)}\Microsoft SDKs\Azure\PowerShell\ServiceManagement\Azure\Services\Microsoft.IdentityModel.Clients.ActiveDirectory.WindowsForms.dll" | |
$null = [System.Reflection.Assembly]::LoadFrom($adal) | |
$null = [System.Reflection.Assembly]::LoadFrom($adalforms) | |
$adTenant = $Subscription.TenantId | |
# Client ID for Azure PowerShell | |
$clientId = "1950a258-227b-4e31-a9cf-717495945fc2" | |
# Set redirect URI for Azure PowerShell | |
$redirectUri = "urn:ietf:wg:oauth:2.0:oob" | |
# Set Resource URI to Azure Service Management API | @marckean | |
$resourceAppIdURIARM = "https://management.core.windows.net/" | |
# Authenticate and Acquire Token | |
# Set Authority to Azure AD Tenant | |
$authority = "https://login.windows.net/$TenantId" | |
# Create Authentication Context tied to Azure AD Tenant | |
$authContext = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext" -ArgumentList $authority | |
# Acquire token | |
$global:Token = $authContext.AcquireToken($resourceAppIdURIARM, $clientId, $redirectUri, "Auto") | |
} | |
# Using AAD Application Service Principal | |
Function RestAPI-SPN-AuthToken ($TenantId) { | |
$Username = $Cred.Username | |
$Password = $Cred.Password | |
# Set Resource URI to Azure Service Management API | |
$resourceAppIdURI = 'https://management.core.windows.net/' | |
# Set Authority to Azure AD Tenant | |
$authority = "https://login.windows.net/$TenantId" | |
# Build up the credentials | |
$ClientCred = [Microsoft.IdentityModel.Clients.ActiveDirectory.ClientCredential]::new($UserName, $Password) | |
# Acquire token | |
$authContext = [Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext]::new($authority) | |
$global:Token = $authContext.AcquireTokenAsync($resourceAppIdURI,$ClientCred) | |
} | |
Function SPNRequestHeader { | |
# Create Authorization Header | |
$authHeader = $global:Token.Result.CreateAuthorizationHeader() | |
# Set HTTP request headers to include Authorization header | @marckean | |
$requestHeader = @{ | |
"x-ms-version" = "2014-10-01"; #'2014-10-01' | |
"Authorization" = $authHeader | |
} | |
return $RequestHeader | |
} | |
Function RequestHeader { | |
# Create Authorization Header | |
# Set HTTP request headers to include Authorization header | @marckean | |
$requestHeader = @{ | |
"Content-Type" = "application/json"; #'2014-10-01' | |
"Authorization" = "Bearer $($global:Token.AccessToken)" | |
} | |
return $RequestHeader | |
} | |
########################################################################################## | |
############################# Roll Through the VMs ############################### | |
########################################################################################## | |
# Get Start Time | |
$startTMD = (Get-Date) | |
"Starting Script: {0:HH:mm MM-dd-yyyy}..." -f $startTMD | |
#region Inventory of Azure VMs | |
$Results = @() | |
$ResultsDataPath = "$env:USERPROFILE\Desktop\" | |
$ResultsFile = "AzureVMList-{0:yyyy_MM_dd_HH-mm}" -f (Get-Date) | |
$ResultsFileExt = ".csv" | |
$ResultsCSV = "$ResultsDataPath$ResultsFile$ResultsFileExt" | |
$ARMsubscriptions = Get-AzureRmSubscription | |
" " | |
"Processing Azure subscriptions for the existence of VM's" | |
foreach($ARMsub in $ARMsubscriptions){ | |
Select-AzureRmSubscription -SubscriptionName $ARMsub.Name | |
########################################################################################## | |
################################ Rest API Token ################################## | |
########################################################################################## | |
switch -Wildcard ($SP_Password) | |
{ | |
?* { | |
RestAPI-SPN-AuthToken $ARMsub.TenantId # To Logon to Rest and get an an auth key | |
$RequestHeader = SPNRequestHeader | |
} | |
default { | |
RestAPI-AuthToken $ARMsub.TenantId | |
$RequestHeader = RequestHeader | |
} | |
} | |
$AzureRmVMResources = Get-AzureRmResource | ? {$_.ResourceType -eq 'Microsoft.Compute/virtualMachines' -or ` | |
$_.ResourceType -eq 'Microsoft.ClassicCompute/virtualMachines' -or ` | |
$_.ResourceType -eq 'Microsoft.Compute/virtualMachines/InstanceView'} | |
foreach($AzureRmVMResource in $AzureRmVMResources){ | |
$RmResource = Get-AzureRmResource -ExpandProperties -ResourceGroupName $AzureRmVMResource.ResourceGroupName ` | |
-ResourceName $AzureRmVMResource.ResourceName -ResourceType $AzureRmVMResource.ResourceType | |
if($RmResource.ResourceType -eq 'Microsoft.ClassicCompute/virtualMachines'){ | |
$w = [PSCustomObject] @{ | |
Subscription_Name = $ARMsub.Name | |
Resource_Name = ($RmResource).ResourceName | |
Deployment_Name = ($RmResource).Properties.HardwareProfile.DeploymentName | |
Computer_Name = ($RmResource).Properties.InstanceView.computerName | |
Fully_Qualified_Domain_Name = ($RmResource).Properties.InstanceView.FullyQualifiedDomainName | |
Resource_Type = ($RmResource).ResourceType | |
OS_Type = ($RmResource).Properties.StorageProfile.OperatingSystemDisk.OperatingSystem | |
OS_Publisher = ($RmResource).Properties.StorageProfile.Publisher | |
OS_Offer = ($RmResource).Properties.StorageProfile.OperatingSystemDisk.SourceImageName | |
OS_SKU = ($RmResource).Properties.StorageProfile.ImageReference.SKU | |
VM_Size = ($RmResource).Properties.HardwareProfile.Size | |
Public_IP_Addresses = ($RmResource).Properties.InstanceView.PublicIpAddresses -join ' ' | |
Private_IP_Address = ($RmResource).Properties.InstanceView.PrivateIpAddress | |
vNetName = ($RmResource).Properties.NetworkProfile.VirtualNetwork.name | |
Location = ($RmResource).Location | |
Provisioning_State = ($RmResource).Properties.InstanceView.Status | |
Power_State = ($RmResource).Properties.InstanceView.powerState | |
} | |
($RmResource).Properties | |
$Results += $w | |
} | |
if($RmResource.ResourceType -eq 'Microsoft.Compute/virtualMachines'){ | |
#### API Call to get the Power State from ARM InstanceView | |
$VMResourceID = $($VirtualMachine.ResourceID) | |
$APIURL = 'https://management.azure.com' | |
$myAPIPath = ` | |
"/subscriptions/$($ARMsub.id)/resourcegroups/$($AzureRmVMResource.ResourceGroupName)/providers/Microsoft.Compute/virtualMachines/$($AzureRmVMResource.name)" | |
$apicall = "$myAPIPath/InstanceView?api-version=2017-03-30" | |
$Uri = "{0}{1}" -f $APIURL, $apicall | |
$VMInstanceView = Invoke-RestMethod -Method Get -Headers $RequestHeader -Uri $uri | |
$w = [PSCustomObject] @{ | |
Subscription_Name = $ARMsub.Name | |
Resource_Name = ($RmResource).ResourceName | |
Deployment_Name = ($RmResource).Name | |
Computer_Name = ($RmResource).Properties.OsProfile.ComputerName | |
Fully_Qualified_Domain_Name = (Get-AzureRmPublicIpAddress | ? {$_.ID -match $RmResource.Name}).DnsSettings.Fqdn | |
Resource_Type = ($RmResource).ResourceType | |
OS_Type = ($RmResource).Properties.StorageProfile.OsDisk.OsType | |
OS_Publisher = ($RmResource).Properties.StorageProfile.ImageReference.Publisher | |
OS_Offer = ($RmResource).Properties.StorageProfile.ImageReference.Offer | |
OS_SKU = ($RmResource).Properties.StorageProfile.ImageReference.SKU | |
VM_Size = ($RmResource).Properties.HardwareProfile.VMSize | |
Public_IP_Addresses = (Get-AzureRmPublicIpAddress | ? {$_.ID -match $RmResource.Name}).IpAddress -join ' ' | |
Private_IP_Address = (Get-AzureRmNetworkInterface | ? {$_.ID -match $RmResource.Name}).IpConfigurations.PrivateIpAddress | |
vNetName = (Get-AzureRmVirtualNetwork | ? {$_.Subnets.ID -match (Get-AzureRmNetworkInterface | ? {$_.ID -match $RmResource.Name}).IpConfigurations.subnet.id}).Name | |
Location = ($RmResource).Location | |
Provisioning_State = ($RmResource).Properties.ProvisioningState | |
Power_State = ($VMInstanceView.statuses | where {$_.code -Like '*power*'}).displayStatus | |
} | |
$Results += $w | |
} | |
} | |
} | |
$Results | |
$Results | Export-Csv -notypeinformation -Path $ResultsCSV | |
#endregion | |
# Get End Time | |
$endTMD = (Get-Date) | |
"Stopping Script: {0:HH:mm MM-dd-yyyy}..." -f $endTMD | |
# Echo Time elapsed | |
"Elapsed Time: $(($endTMD-$startTMD).totalseconds) seconds" | |
# Inventory output path confirmation | |
" " | |
"Inventory output to $ResultsCSV" |
Description
There seems to be a small problem with the PowerShell version tester here and I imagine there are better ways to handle it, but I shall address the most simple solution below
Problem
I am assuming the first version in the list will be the highest, but I am not sure if it tells you which one is currently being used. I know there is a distinction between -list (meaning installed) and -ListAvailable (meaning available for install). So let's assume we only want to know the version they have installed, but the corner case here would be that the user has multiple versions of PowerShell installed.
What would that look like?
$ReqVersions = Get-Module Azure -list | Select-Object Version, PowerShellVersion
Command Output Result:
$ReqVersions
Version PowerShellVersion
5.3.0 5.0
5.1.2 3.0
Solution
So now checking to see if the version matches a singular to a list as shown below will fail.
`$ReqVersions = Get-Module Azure -list | Select-Object Version, PowerShellVersion
Current PowerShell version must be higher then the one required by the Azure Module
if($PSVersionTable.PSVersion.Major -lt $ReqVersions.PowerShellVersion.Major)
{
$PSVerReq = $ReqVersions.PowerShellVersion
$PSVerInst = $PSVersionTable.PSVersion
"Validation failed..."
"Installed PowerShell version: $PSVerInst"
"Powershell version $PSVerReq required. Please update the version of Powershell on this system"
"Exiting Script"
Break
} `
Command Output Result: Error can't compare an object multiple objects which presents you with a type error.
Could not compare "5" to "5 3". Error: "Cannot convert the "System.Object[]" value of type
"System.Object[]" to type "System.Int32"."
At line:1 char:4
- if($PSVersionTable.PSVersion.Major -lt $ReqVersions.PowerShellVersion ...
-
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : ComparisonFailure
So, one solution would be to limit the number of results where you presume the highest version is always the first result.
$ReqVersions = (Get-Module azure -list | Select-Object Version, PowerShellVersion)[0]
Command Output Result:
$ReqVersions
Version PowerShellVersion
5.3.0 5.0
Conclusion
Limiting the list to one using the first instance of the object i.e. (code)[0] should solve the corner case. I hope that helps.
:-)
I had to change this call
Select-AzureRmSubscription -SubscriptionName $ARMsub.SubscriptionName
(line 36 at the time of writing this)
to:
Select-AzureRmSubscription -SubscriptionName $ARMsub.Name
You can see the property-name(s) with the below code (just after the for-each begins)
$ARMsub.PSObject.Properties
Write-Host "Name - " + $ARMsub.Name
Write-Host "SubscriptionId - " + $ARMsub.SubscriptionId
Write-Host "TenantId - " + $ARMsub.TenantId
Select-AzureRmSubscription -SubscriptionName $ARMsub.Name
But otherwise a very useful script. Thank you!