Home > database >  Optimize PowerShell code to avoid calling the cmdlet multiple times inside calculated properties?
Optimize PowerShell code to avoid calling the cmdlet multiple times inside calculated properties?

Time:08-18

I need some suggestions and help in optimizing the code below to avoid calling the same command twice under the calculated properties.

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

https://docs.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.

If anyone knows how to do it better, please suggest the updated code.

Thanks.

CodePudding user response:

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.

CodePudding user response:

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.

  • Related