skip to Main Content

I am looking to optimize the code below to avoid calling the same command twice under the calculated properties.

https://learn.microsoft.com/en-us/powershell/module/az.compute/get-azvm

https://learn.microsoft.com/en-us/powershell/module/az.compute/get-azvmsize

Get-AzVM | Select-Object-Object Name,
    @{ l = 'osdiskingb'; e = { ($_.StorageProfile.OsDisk.DiskSizeGB) } }, `
    @{ l = 'memory'; e = { $size = $_.HardwareProfile.VmSize; Get-AzVMSize -vmname $_.Name -ResourceGroupName $_.ResourceGroupName | Where-Object { $_.name -eq $size } | Select-Object -expand MemoryInMB } }, `
    @{ l = 'cpu'; e = { $size = $_.HardwareProfile.VmSize; Get-AzVMSize -vmname $_.Name -ResourceGroupName $_.ResourceGroupName | Where-Object { $_.name -eq $size } | Select-Object -expand NumberOfCores } }, `
    @{ l = 'nic'; e = { $_.NetworkProfile.NetworkInterfaces.id.split('/') | Select-Object -Last 1 } }, `
    @{ l = 'ip'; e = { $nic = $_.NetworkProfile.NetworkInterfaces.id.split('/') | Select-Object -Last 1; Get-AzNetworkInterface -Name $nic | Select-Object -expand ipconfigurations | Select-Object -expand privateipaddress } }

The script above works for pulling various different Azure VMs.

What can I try next?

2

Answers


  1. Note:

    • This answer addresses the question as asked, in the context of Select-Object and calculated properties.

    • For a ForEach-Object-based alternative that uses explicit construction of [pscustomobject] instances, see zett42’s helpful answer.


    While the script blocks of calculated properties are executed in sequence, for each input object, they each run in their own child scope relative to the caller, which complicates sharing state between them.

    However, you can simply create a variable whose value you want to share in the parent scope, which in the simplest case inside a script is the $script: scope, as the following simplified example shows (which uses a call to Get-Date in lieu of a call to Azure cmdlet as an example of a call you do not want to repeat):

    # Share the result of the `Get-Date` call between calculated properties.
    'foo' | Select-Object `
      @{ n='Month'; e = { $script:dt = Get-Date; $dt.Month } },
      @{ n='Year'; e = { $dt.Year } }
    

    Output:

    Month Year
    ----- ----
        8 2022
    

    This proves that the $script:-scoped $dt variable was successfully used in the second calculated property.

    If you want to reliably target the parent scope, which may differ from the $script: scope if you’re running inside a nested function call, for instance, replace $script:dt = Get-Date with
    Set-Variable -Scope 1 dt (Get-Date)

    Note:

    • That script blocks of calculated properties as well as delay-bind script blocks run in a child scope may be surprising, given that it contrasts with the behavior of script blocks passed to ForEach-Object and Where-Object, for instance – for a discussion, see GitHub issue #7157.
    Login or Signup to reply.
  2. This might not exactly answer your original question, but you might consider dropping calculated properties when the code becomes too complicated. Instead, use a [pscustomobject]@{…} literal in a ForEach-Object script block. This way you can move common code out of the properties to the begin of the script block.

    Get-AzVM | ForEach-Object {
        $size = $_.HardwareProfile.VmSize
        $vmsize = Get-AzVMSize -vmname $_.Name -ResourceGroupName $_.ResourceGroupName | Where-Object { $_.name -eq $size }
        $nic = $_.NetworkProfile.NetworkInterfaces.id.split('/') | Select-Object -Last 1
    
        # Implicitly outputs an object with the given properties
        [pscustomobject]@{
            Name       = $_.Name
            osdiskingb = $_.StorageProfile.OsDisk.DiskSizeGB
            memory     = $vmsize.MemoryInMB
            cpu        = $vmsize.NumberOfCores
            nic        = $nic
            ip         = (Get-AzNetworkInterface -Name $nic).ipconfigurations.privateipaddress  
        } 
    }
    

    On a side note, SomeCommand | Select-Object -Expand PropertyName isn’t very efficient and can be replaced by member access, as I did for the ip property. The key is to enclose the command in parentheses.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search