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
andWhere-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.