Vineet Agrawal
asked on
List all azure resources with tagnames (case-insensitive) and tagvalues (case-sensitive) using PowerShell
Hello,
My following script is successfully working. But it is not reading the Tags fields with case-insensitive.
$resources = Get-AzureRmResource | Where-Object {($_.ResourceType -eq "Microsoft.Compute/virtual Machines") -or ($_.ResourceType -eq "Microsoft.Sql/servers/dat abases")} |
foreach {
new-object -TypeName psobject -Property @{
ResourceName = $_.ResourceName;
ResourceType = $_.ResourceType;
OperatingHours =
if ( ($_ | select -expand Tags).OperatingHours -ieq $null )
{"TAG NOT PRESENT"}
elseif ( ($_ | select -expand Tags).OperatingHours -ieq '')
{"NULL/EMPTY"}
else
{($_ | select -expand Tags).OperatingHours} ;
}
}
$resources | Format-Table
The Output of above script is :
ResourceType ResourceName OperatingHours
------------------------ ------------------------ ------------------
Microsoft.Compute/virtualM achines asa-perfvm16 TAG NOT PRESENT
Microsoft.Compute/virtualM achines OAASLogicApps1 TAG NOT PRESENT
Microsoft.Compute/virtualM achines VMForPerfTest TAG NOT PRESENT
Here, some resources do not have 'OperatingHours', rather they have 'operatinghours' or 'Operatinghours' or any other case-combination. This value is set by users, so it can be of any case-combination.
My above script is not fetching proper data in relation to case-insensitiveness of Tags.Key. I am not concerned with the case of Tags.Value.
I tried to modify the above script with following but still the output is same. The three resource-list that is appearing in output; all resources are created by me so I am sure that concerned Tag is present in all of them but with different cases.
Modified Script (but no success):
$resources = Get-AzureRmResource | Where-Object {($_.ResourceType -eq "Microsoft.Compute/virtual Machines") -or ($_.ResourceType -eq "Microsoft.Sql/servers/dat abases")} |
foreach {
$rTags = @{}
foreach($key in $resources.Tags.key)
{
$rTags.Add($key.ToLower(), $resources.Tags[$key].ToLo wer())
}
new-object -TypeName psobject -Property @{
ResourceName = $_.ResourceName;
ResourceType = $_.ResourceType;
env =
if ( $rTags.ENV -ieq $null )
{"TAG NOT PRESENT"}
elseif ( $rTags.ENV -ieq '')
{"NULL/EMPTY"}
else
{$rTags.ENV} ;
}
}
$resources | Format-Table
My following script is successfully working. But it is not reading the Tags fields with case-insensitive.
$resources = Get-AzureRmResource | Where-Object {($_.ResourceType -eq "Microsoft.Compute/virtual
foreach {
new-object -TypeName psobject -Property @{
ResourceName = $_.ResourceName;
ResourceType = $_.ResourceType;
OperatingHours =
if ( ($_ | select -expand Tags).OperatingHours -ieq $null )
{"TAG NOT PRESENT"}
elseif ( ($_ | select -expand Tags).OperatingHours -ieq '')
{"NULL/EMPTY"}
else
{($_ | select -expand Tags).OperatingHours} ;
}
}
$resources | Format-Table
The Output of above script is :
ResourceType ResourceName OperatingHours
------------------------ ------------------------ ------------------
Microsoft.Compute/virtualM
Microsoft.Compute/virtualM
Microsoft.Compute/virtualM
Here, some resources do not have 'OperatingHours', rather they have 'operatinghours' or 'Operatinghours' or any other case-combination. This value is set by users, so it can be of any case-combination.
My above script is not fetching proper data in relation to case-insensitiveness of Tags.Key. I am not concerned with the case of Tags.Value.
I tried to modify the above script with following but still the output is same. The three resource-list that is appearing in output; all resources are created by me so I am sure that concerned Tag is present in all of them but with different cases.
Modified Script (but no success):
$resources = Get-AzureRmResource | Where-Object {($_.ResourceType -eq "Microsoft.Compute/virtual
foreach {
$rTags = @{}
foreach($key in $resources.Tags.key)
{
$rTags.Add($key.ToLower(),
}
new-object -TypeName psobject -Property @{
ResourceName = $_.ResourceName;
ResourceType = $_.ResourceType;
env =
if ( $rTags.ENV -ieq $null )
{"TAG NOT PRESENT"}
elseif ( $rTags.ENV -ieq '')
{"NULL/EMPTY"}
else
{$rTags.ENV} ;
}
}
$resources | Format-Table
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
ASKER
Thanks @footech
Allow me to be more clear here.
Resources have multiple tags ( total 9 different tags to be specific). I just gave an example by using only OperatingHours here. As you said, tags is a hashtable and all 9 tags are present in this 'Tags' property. It is possible that a particular resource does not have 1 or 2 tags out of all 9 and that's what I am filtering out in 'Tag not present'.
If ($_.tags) {$tag = @{} + $_.tags}
$_.psobject.properties.nam e -notcontains "Tags"
Setting an empty string is not possible through Azure portal, but it is possible if the done through Azure-Powershell. Users run scripts all the time to update their tags through powershell which creates a possibility of empty string in a particular tag-value. And hence arises another scenario of Tag.Key in various cases rather than Tag.Value.
I have tried using
Here is an example for better understanding:
Allow me to be more clear here.
Resources have multiple tags ( total 9 different tags to be specific). I just gave an example by using only OperatingHours here. As you said, tags is a hashtable and all 9 tags are present in this 'Tags' property. It is possible that a particular resource does not have 1 or 2 tags out of all 9 and that's what I am filtering out in 'Tag not present'.
If ($_.tags) {$tag = @{} + $_.tags}
This condition will always be true because a particular resource will definitely have some/other tags (out of all 9 as mentioned above) in $_.tags
$_.psobject.properties.nam
This statement checks if 'Tags' is present, but I need to check if 'OperatingHours' within the Tags hashtable is present !
Setting an empty string is not possible through Azure portal, but it is possible if the done through Azure-Powershell. Users run scripts all the time to update their tags through powershell which creates a possibility of empty string in a particular tag-value. And hence arises another scenario of Tag.Key in various cases rather than Tag.Value.
I have tried using
$_.Tags.GetEnumerator()
as you suggested but this also is not checking for all case-possibilities in Tags.Key. Here is an example for better understanding:
Resource-1:
ResourceName : VM1
ResourceType : Microsoft.Compute/virtualM achines
Tags : {OperatingHours, env, appID, orgID, ...}
Resource-2:
ResourceName : VM2
ResourceType : Microsoft.Compute/virtualM achines
Tags : {operatinghours, ENV, appID, orgID, ...}
Resource-3:
ResourceName : DB3
ResourceType : Microsoft.Sql/servers/data bases
Tags : {Env, appID, orgID, ...} does not have operatinghours
The output I am getting right now is ( I am just showing 2 our of all 9 tags for ease):
But what I need is :
ResourceName : VM1
ResourceType : Microsoft.Compute/virtualM
Tags : {OperatingHours, env, appID, orgID, ...}
Resource-2:
ResourceName : VM2
ResourceType : Microsoft.Compute/virtualM
Tags : {operatinghours, ENV, appID, orgID, ...}
Resource-3:
ResourceName : DB3
ResourceType : Microsoft.Sql/servers/data
Tags : {Env, appID, orgID, ...} does not have operatinghours
The output I am getting right now is ( I am just showing 2 our of all 9 tags for ease):
ResourceType ResourceName OperatingHours env
--------------------------------- ------------- --------------- ---------------
Microsoft.Compute/virtualMachines VM1 09-17 UAT
Microsoft.Compute/virtualMachines VM2 TAG NOT PRESENT TAG NOT PRESENT
Microsoft.Sql/servers/databases DB3 TAG NOT PRESENT TAG NOT PRESENT
But what I need is :
ResourceType ResourceName OperatingHours env
--------------------------------- ------------- --------------- ---------------
Microsoft.Compute/virtualMachines VM1 09-17 UAT
Microsoft.Compute/virtualMachines VM2 09-17 dev
Microsoft.Sql/servers/databases DB3 TAG NOT PRESENT test
ASKER
I knew it had to be a very small change. Following is the updated script that is helping me fetch required output
$resources = Get-AzureRmResource | Where-Object {($_.ResourceType -eq "Microsoft.Compute/virtualMachines") -or ($_.ResourceType -eq "Microsoft.Sql/servers/databases")} |
foreach {
[hashtable] $rTags = @{}
foreach($key in $_.Tags.keys)
{
$rTags.Add($key.ToLower(), $_.Tags[$key].ToLower())
}
new-object -TypeName psobject -Property @{
ResourceName = $_.ResourceName;
ResourceType = $_.ResourceType;
operatinghours =
if ( ! $rTags.ContainsKey("operatinghours") )
{"TAG NOT PRESENT"}
elseif ( $rTags.operatinghours -ieq '' -or $rTags.operatinghours -ieq $null)
{"NULL/EMPTY"}
else
{ $rTags.operatinghours } ;
env =
if ( ! $rTags.ContainsKey("env") )
{"TAG NOT PRESENT"}
elseif ( $rTags.env -ieq '' -or $rTags.env -ieq $null)
{"NULL/EMPTY"}
else
{ $rTags.env } ;
}
}
$resources | Format-Table
Sounds like you've worked it out.
I just wanted to explain a few pieces.
If ($_.tags) {$tag = @{} + $_.tags}
If you always have tags, then yes, this would always be true, but the more important point is that it's creating a case-insensitive hashtable ($tag) from the case-sensitive one. Your last also creates a case-insensitive hashtable, but also converts everything to lower-case.
Good to know that setting an empty value is possible through PS. It might be worth standardizing the script(s) they use so that the tags set are always the same case.
The one thing about using the GetEnumerator() method is that it allows you to pass the hashtable to the pipeline and filter using Where-Object against the name of the key using case-insensitivity. However, I do like the method of creating a new case-insensitive hashtable better as it's more readable.
Here's a modification so you don't have to duplicate so much code for each tag you want to check. Just adjust the $checkTags variable to include all the tags you want to check. Also, you know that an empty string and $null have no case, right? So whether you use -eq, -ieq, or -ceq the result will be the same.
I just wanted to explain a few pieces.
If ($_.tags) {$tag = @{} + $_.tags}
If you always have tags, then yes, this would always be true, but the more important point is that it's creating a case-insensitive hashtable ($tag) from the case-sensitive one. Your last also creates a case-insensitive hashtable, but also converts everything to lower-case.
This statement checks if 'Tags' is present, but I need to check if 'OperatingHours' within the Tags hashtable is present !OK, I misunderstood what you were after.
Good to know that setting an empty value is possible through PS. It might be worth standardizing the script(s) they use so that the tags set are always the same case.
The one thing about using the GetEnumerator() method is that it allows you to pass the hashtable to the pipeline and filter using Where-Object against the name of the key using case-insensitivity. However, I do like the method of creating a new case-insensitive hashtable better as it's more readable.
Here's a modification so you don't have to duplicate so much code for each tag you want to check. Just adjust the $checkTags variable to include all the tags you want to check. Also, you know that an empty string and $null have no case, right? So whether you use -eq, -ieq, or -ceq the result will be the same.
$checkTags = "OperatingHours","Env"
$resources = Get-AzureRmResource | Where-Object {($_.ResourceType -eq "Microsoft.Compute/virtualMachines") -or ($_.ResourceType -eq "Microsoft.Sql/servers/databases")} |
foreach {
If ($_.tags) {$rtags = @{} + $_.tags}
$properties = @{
ResourceName = $_.ResourceName
ResourceType = $_.ResourceType
}
$checkTags | ForEach `
{
$properties.Add($_, "$(if ( ! $rTags.ContainsKey($_) )`
{"TAG NOT PRESENT"} `
elseif ( $rTags.$_ -eq '' -or $rTags.$_ -eq $null)`
{"NULL/EMPTY"} `
else `
{ $rTags.$_ })" )
}
New-Object -TypeName psobject -Property $properties
}
$resources | Format-Table
SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Yes, the ToLower() method is not needed to avoid duplicate properties. Once we've created the new object (with the New-Object command), all the handling of its properties is case insensitive (case aware but case insensitive).
In the example above you can see the two tags "ENV" and "operatinghours". I had one VM that had the tags "ENV" and "operatinghours", and another VM that had tags "env" and "operatingHOURS". So it grabbed the values without regard to the case of the tags, which of course is what we've been after. The case that it uses to display the property names (obtained via the Get-AzureRmTag command) matches the variation that is first retrieved by that command (in my scenario matching what was set on my first VM).
Now, when it comes to formatting just the output (adjusting the case of property names and values), that is another matter and can also be done. If you want to standardize the case of the property names in the final output (and not rely just on whatever the result from Get-AzureRmTag is), then you can certainly do that. For example with this little mod.
I have not played around with this at all, but from reading the online documentation for the Get-AzureRmTag cmdlet, it appears it might be possible to define your tags and not allow this variation to occur in the first place.
In the example above you can see the two tags "ENV" and "operatinghours". I had one VM that had the tags "ENV" and "operatinghours", and another VM that had tags "env" and "operatingHOURS". So it grabbed the values without regard to the case of the tags, which of course is what we've been after. The case that it uses to display the property names (obtained via the Get-AzureRmTag command) matches the variation that is first retrieved by that command (in my scenario matching what was set on my first VM).
Now, when it comes to formatting just the output (adjusting the case of property names and values), that is another matter and can also be done. If you want to standardize the case of the property names in the final output (and not rely just on whatever the result from Get-AzureRmTag is), then you can certainly do that. For example with this little mod.
$checkTags = Get-AzureRmTag | ForEach { $_.Name.ToLower() } | Sort
You could make the same kind of adjustment to the values of the tags retrieved as well.I have not played around with this at all, but from reading the online documentation for the Get-AzureRmTag cmdlet, it appears it might be possible to define your tags and not allow this variation to occur in the first place.
If the subscription includes any predefined tags, you cannot apply undefined tags or values to any resource or resource group in the subscription.
No comment has been added to this question in more than 21 days, so it is now classified as abandoned.
I have recommended this question be closed as follows:
Split:
-- footech (https:#a42144021)
-- Vineet Agrawal (https:#a42150948)
If you feel this question should be closed differently, post an objection and the moderators will review all objections and close it as they feel fit. If no one objects, this question will be closed automatically the way described above.
Pber
Experts-Exchange Cleanup Volunteer
I have recommended this question be closed as follows:
Split:
-- footech (https:#a42144021)
-- Vineet Agrawal (https:#a42150948)
If you feel this question should be closed differently, post an objection and the moderators will review all objections and close it as they feel fit. If no one objects, this question will be closed automatically the way described above.
Pber
Experts-Exchange Cleanup Volunteer
Open in new window