Link to home
Start Free TrialLog in
Avatar of Vineet Agrawal
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/virtualMachines") -or ($_.ResourceType -eq "Microsoft.Sql/servers/databases")} |
 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/virtualMachines        asa-perfvm16                    TAG NOT PRESENT
Microsoft.Compute/virtualMachines        OAASLogicApps1              TAG NOT PRESENT
Microsoft.Compute/virtualMachines        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/virtualMachines") -or ($_.ResourceType -eq "Microsoft.Sql/servers/databases")} |
 foreach {
         
        $rTags = @{}
            foreach($key in $resources.Tags.key)
            {
                      $rTags.Add($key.ToLower(), $resources.Tags[$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
Avatar of footech
footech
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Here's another variation, basically creating a case-insensitive hashtable from the case-sensitive one.
Get-AzureRmResource | Where-Object {($_.ResourceType -eq "Microsoft.Compute/virtualMachines") -or ($_.ResourceType -eq "Microsoft.Sql/servers/databases")} |
 foreach {
    If ($_.tags) {$tag = @{} + $_.tags}
    new-object -TypeName psobject -Property @{
                ResourceName       =       $_.ResourceName;
                ResourceType       =       $_.ResourceType;
                OperatingHours  =  
                    if ( $_.psobject.properties.name -notcontains "Tags" )
                    {"TAG NOT PRESENT"}
                    elseif ( $tag.OperatingHours -eq "")
                    {"NULL/EMPTY"}
                    else
                    { $tag.OperatingHours }
            }
          }

Open in new window

Avatar of Vineet Agrawal
Vineet Agrawal

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}
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.name -notcontains "Tags"
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()

Open in new window

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/virtualMachines
Tags : {OperatingHours, env, appID, orgID, ...}

Resource-2:
ResourceName : VM2
ResourceType : Microsoft.Compute/virtualMachines
Tags : {operatinghours, ENV, appID, orgID, ...}

Resource-3:
ResourceName : DB3
ResourceType : Microsoft.Sql/servers/databases
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

Open in new window


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

Open in new window

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

Open in new window

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.

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

Open in new window

SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
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).
User generated imageIn 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

Open in new window

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