Search code examples
powershellvmwarepowercli

Object to hashtable key comparison


I'm looking for some help troubleshooting comparing .key values to objects.

Basically what's happening here is I'm connecting to two VMware vCenters and downloading a list of roles and putting those roles into two hash tables, then comparing them.

The problem comes down to the Process-Roles function where the comparing logic is flawed somewhere. It outputs all of the roles in both lists. I think the (-not .containskey) isn't working right. I've debugged in powerGUI and both hashtables and mstr_roles/slave_roles are all filled correctly.

The roles lists should be object lists, as they were filled with Get-VIRole. The hash table should be object-in-key, value null lists. Is it possible to compare these two? I'm trying to check if the $role object in the roles list exists in the .key values list of the hash table.

$creds = Get-Credential
$mst = Read-Host "`n Master Server: "
$slv = Read-Host "`n Slave Server: "
$hsh_mstr_roles = @{}
$hsh_slave_roles = @{}
$mstr_roles = ""
$slave_roles = ""

Get-Roles -MasterServer $mst -SlaveServer $slv

Process-Roles 

.

function Get-Roles() {
Param(
 [Parameter(Mandatory=$True,Position=0)]
 [string]$MasterServer,

 [Parameter(Mandatory=$True,Position=1)]
 [string]$SlaveServer
 )

#Get Master Roles
Connect-VIServer $MasterServer -Credential $creds

$mstr_roles = Get-VIrole

foreach ($role in $mstr_roles) {
    $hsh_mstr_roles.add($role, $null)
}

Disconnect-VIServer $MasterServer -Confirm:$false

#Get Slave Roles
Connect-VIServer $SlaveServer -Credential $creds

$slave_roles = Get-VIrole

foreach ($role in $slave_roles) {
    $hsh_slave_roles.add($role, $null)
}

Disconnect-VIServer $SlaveServer -Confirm:$false

Write-Host "`n + Retrieved Roles Successfully"
}    

.

function Process-Roles () { 
#Get Roles on Master NOT ON SLAVE

Write-Host "`n"

foreach ($role in $mstr_roles){
    if(-not $hsh_slave_roles.containsKey($role)){
    Write-Host $role "doesn't exist on slave"
    }
}

#Get Roles on Slave NOT ON MASTER
foreach ($role in $slave_roles){
    if(-not $hsh_mstr_roles.containsKey($role)){
    Write-Host $role "doesn't exist on master"
    }
}

Write-Host "`n + Processed Roles Successfully"
}

Solution

  • The easiest way to do this is by finding the complement to one of the two sets of Keys that each hashtable has, using -notcontains:

    function Process-Roles {
        param(
            [hashtable]$MasterRoles,
            [hashtable]$SlaveRoles
        )
    
        # Complement to slave roles (those ONLY in $MasterRoles)
        $MasterRoles.Keys |Where-Object { $SlaveRoles -notcontains $_ }|ForEach-Object {
            Write-Host "$_ not in Slave Roles"
        } 
    
        # and the other way around (those ONLY in $SlaveRoles)
        $SlaveRoles.Keys |Where-Object { $MasterRoles -notcontains $_ }|ForEach-Object {
            Write-Host "$_ not in Master Roles"
        } 
    
    }
    

    I'll have to add that your way of working with variables in different scopes is sub-optimal.

    1. Define the parameters that the function needs in order to "do its job"
    2. Return output from your functions where it make sense (any Get-* function should at least)
    3. Depend on the Global and Script scopes as little as possible, preferably not at all

    I would go with something like this instead:

    Add a Credential parameter to the Get-Roles function and return the results rather than modifying a variable in a parent scope (here, using a Hashtable of role categories):

    function Get-Roles {
        Param(
            [Parameter(Mandatory=$True,Position=0)]
            [string]$MasterServer,
    
            [Parameter(Mandatory=$True,Position=1)]
            [string]$SlaveServer,
    
            [Parameter(Mandatory=$True,Position=2)]
            [pscredential]$Credential
        )
    
        $DiscoveredRoles = @{}
    
        # Get Master Roles
        Connect-VIServer $MasterServer -Credential $Credential
        $DiscoveredRoles["MasterRoles"] = Get-VIRole
        Disconnect-VIServer $MasterServer -Confirm:$false
    
        #Get Slave Roles
        Connect-VIServer $SlaveServer -Credential $Credential
        $DiscoveredRoles["SlaveRoles"] = Get-VIrole
        Disconnect-VIServer $SlaveServer -Confirm:$false
    
        Write-Verbose "`n + Retrieved Roles Successfully"
    
        return $DiscoveredRoles
    
    }    
    

    Define parameters for the Process-Roles function, that match the hashtable you expect to generate from Get-Roles and do the same comparison of the role names as above, only this time we grab them directly from the Role objects:

    function Process-Roles { 
        param(
            [Parameter(Mandatory=$true)]
            [ValidateScript({ $_.ContainsKey("MasterRoles") -and $_.ContainsKey("SlaveRoles") })]
            [hashtable]$RoleTable
        )
    
        $MasterRoleNames = $RoleTable["MasterRoles"] |Select-Object -ExpandProperty Name
    
        $SlaveRoleNames = $RoleTable["SlaveRoles"] |Select-Object -ExpandProperty Name
    
        $MasterRoleNames |Where-Object { $SlaveRoleNames -notcontains $_ } |ForEach-Object {
            Write-Host "$_ doesn't exist on slave"    
        }
    
        $SlaveRoleNames |Where-Object { $MasterRoleNames -notcontains $_ } |ForEach-Object {
            Write-Host "$_ doesn't exist on Master"    
        }
    
        Write-Host "`n + Processed Roles Successfully"
    }
    

    Update your executing script with the new parameters:

    $creds = Get-Credential
    $MasterServer = Read-Host "`n Master Server: "
    $SlaveServer  = Read-Host "`n Slave Server: "
    
    $RoleTable = Get-Roles -MasterServer $MasterServer -SlaveServer $SlaveServer -Credential $creds
    
    Process-Roles -RoleTable $RoleTable
    

    Next step would be to add pipeline support to the Process-Roles function, converting Write-Host statements to Write-Verbose and adding error handling, but I'll leave that as an exercise to OP :-)