How do i get a list of Vm in a recourse group - azure

Im trying to get all Vms in a resource group then send every element to a function
$a = Get-AzureRmVM -ResourceGroupName Test2 | ft Name
foreach($output in $a) {Stop-AzRmVM -ResourceGroupName "Test2" -Name $output}
Im getting this error message
Start-AzureRmVM : Cannot convert 'System.Object[]' to the type 'System.String' required by parameter 'Name'. Specified
method is not supported.

Anish K is correct about the Name parameter expecting a String, not an array of objects.
However, you should also remove the | ft Name because that is only for outputting stuff to console.
I'd use a ForEach-Object here like this (untested):
Get-AzureRmVM -ResourceGroupName 'Test2' | ForEach-Object {
$_ | Stop-AzRmVM
}

As per the error, you are passing an object[] instead of string. Your function Stop-AzRmVm expects string for Name parameter.
Changes your script like below:
$a = Get-AzureRmVM -ResourceGroupName Test2 | ft Name
foreach($output in $a) {Stop-AzRmVM -ResourceGroupName "Test2" -Name $output.Name}

Related

Powershell workflow with "foreach -parallel" and Invoke-AZVMRunCommand- how to get results out?

I have a bunch of VMs in Azure I want to run the same script against. Because of reasons, I can't use powershell 7, so I have to use powershell 5. Which means I can use "foreach -parallel", but NOT "foreach-parallel". So it can work, but requires a workflow.
I have the following workflow that will grab the list of servers in a specific tenant/environment, and then run a foreach -parallel to run a script against each of them at the same time.
The problem I'm running into is that it appears to work, but the result I get out is:
Microsoft.Azure.Commands.Compute.Automation.Models.PSRunCommandResult
blah
Which is not the actual result (in this it would be the time). Because it's within a workflow, getting the details out is... painful. How do I get the results? It appears to run, there's no errors, but I can't figure out the results. Thanks.
The get-member on $out gives:
Name MemberType Definition
---- ---------- ----------
GetType Method type GetType()
ToString Method string ToString(), string ToString(string format, System.IFormatProvider formatProvider), string IFormattable.ToString(string format, System.I...
PSComputerName NoteProperty string PSComputerName=localhost
PSShowComputerName NoteProperty bool PSShowComputerName=True
PSSourceJobInstanceId NoteProperty guid PSSourceJobInstanceId=0870d1ff-1234-5678-014e-2e123456c7d8
Capacity Property System.Int32 {get;set;}
Count Property System.Int32 {get;set;}
EndTime Property {get;set;}
Error Property {get;set;}
Item Property {get;set;}
Name Property {get;set;}
Output Property {get;set;}
StartTime Property {get;set;}
Status Property System.String {get;set;}
Value Property Deserialized.System.Collections.Generic.List`1[[Microsoft.Azure.Management.Compute.Models.InstanceViewStatus, Microsoft.Azure.Management.Compu...
Microsoft.Azure.Commands.Compute.Automation.Models.PSRunCommandResult
And here's the script I'm running:
#for some reason you need to disconnect from one and then connect to the other, can't run two at once.
disconnect-azaccount
connect-azaccount -Tenant "mytenantid"
#get all the dev Windows VM servers in the current tenant that are turned on.
$serverinfo = #()
Get-AzContext -ListAvailable |where-object {$_.Name -like "*Dev*"}| %{
$_|select-azcontext
$serverinfo += get-azvm -status | Where-Object {$_.PowerState -eq "VM running" -and $_.StorageProfile.OSDisk.OSType -eq "Windows"}
}
#honestly, for the test I filtered down to the jumpboxes.
$serverinfo2 = $serverinfo|where {$_.Name -like "*jumpbox*"}
#workflows are needed; can't run foreach -parallel otherwise.
Workflow TestParallel{
#need to pass in the details, it can't reach the $serverinfo2 otherwise.
param($listofservers) #take serverinfo2 and make it accessible from within the workflow.
$test=#()
#need to set the proper context
Get-AzContext -ListAvailable |where-object {$_.Name -like "*Dev*"}| select-azcontext
#now we run each of these at the same time. Foreach-parallel requires 7, but we can do foreach -parallel within powershell 4 workflows.
Foreach -parallel($server in $listofservers){
#now that the script is on there, run the command locally.
$out = Invoke-AzVMRunCommand `
-ResourceGroupName $server.ResourceGroupName `
-Name $server.name `
-CommandId 'RunPowerShellScript' `
-ScriptString "$dt = gwmi win32_localtime; New-Object DateTime $dt.year,$dt.month,$dt.day,$dt.hour,$dt.minute, $dt.second"
#scriptstring is a newer command, added in july 2022
#Formating the Output with the VM name. Value[0].Message contains the results from running the script.
#export it to a variable that will survive the foreach
#$WORKFLOW:test += $server.Name + " " + $out.Value[0].Message
$WORKFLOW:test += $out.Value[1].Message #appears to be 1 now, using " -scriptstring"
#writing it locally to see if it shows up
"$out"
}
[array]$resultsArray = #($test)
write-output ("blah " + $test[0])
$resultsArray
}
TestParallel $serverinfo2

Powershell ForEach-Object: You cannot call a method on a null-valued expression

I am trying to extract the tag values for SystemOwner and TechnicalOwner.
However I get the error "ForEach-Object: You cannot call a method on a null-valued expression"
I already know that the "$Tags = $RG.Tags" could be the issue because running this variable does not give me any results. Then how do I get the resource group tags?
$RGs = (Get-AzResourceGroup).ResourceGroupName
ForEach-Object $RG in $RGs
{
# Get email address value for each SystemOwner and TechnicalOwner Tag in the RG
$Tags = $RG.Tags
$TO = ($Tags.GetEnumerator() | Where-Object {$_.Key -eq "Technical*"}).value
If ($TO)
{
if ($TO.Value -ne $null) {
Write-Host "Technical Owner Found in the Resource Group, building an Array"
$contactemailadress += $TO.Value
}
}
$SO = $Tags.GetEnumerator() | Where-Object {$_.Key -eq "SystemOwner"} #looking for Resource Group tag Names that have a space in the name
If ($SO)
{
if ($SO.Value -ne $null) {
Write-Host "System Owner Found in the Resource Group, building an Array"
$contactemailadress += $SO.Value
}
}
}
I think you could do something like this:
$contactemailadress = #()
$RGs = Get-AzResourceGroup
$RGs | ForEach-Object {
# Get email address value for each SystemOwner and TechnicalOwner Tag in the RG
$Tags = $_.Tags
If ($Tags -and $Tags['TechnicalOwner'])
{
Write-Host "Technical Owner Found in the Resource Group, building an Array"
$contactemailadress += $Tags['TechnicalOwner']
}
If ($Tags -and $Tags['SystemOwner'])
{
Write-Host "System Owner Found in the Resource Group, building an Array"
$contactemailadress += $Tags['SystemOwner']
}
}
The main issue you have is that (Get-AzResourceGroup).ResourceGroupName returns the Resource Group Name - however, in order for the tags to be returned you would need the full object.
You can prove this by checking for Properties on the Get-AzResourceGroup command you are running:
(Get-AzResourceGroup -Name resource_group_name).ResourceGroupName | Get-Member -Type Property
TypeName: System.String
Name MemberType Definition
---- ---------- ----------
Length Property int Length {get;}
As you can see, this command only has one property - length, which is not useful for your scripts purpose.
If you run this instead, you get the full object with all properties:
Get-AzResourceGroup -Name resource_group_name | Get-Member -Type Property
TypeName: Microsoft.Azure.Commands.ResourceManager.Cmdlets.SdkModels.PSResourceGroup
Name MemberType Definition
---- ---------- ----------
Location Property string Location {get;set;}
ManagedBy Property string ManagedBy {get;set;}
ProvisioningState Property string ProvisioningState {get;set;}
ResourceGroupName Property string ResourceGroupName {get;set;}
ResourceId Property string ResourceId {get;set;}
Tags Property hashtable Tags {get;set;}
TagsTable Property string TagsTable {get;}
You can use foreach or ForEach-Object, both should work but when using the latter you can query the variable inside the loop using the $_ notation.
Lastly, in your script there seem to be some variables unnecessarily used (but I don't know if you would need them later on), such as $TO and two if-statements that more or less do the same (i.E. If ($SO) and if ($SO.Value -ne $null)).

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

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

Export-CSV showing System.Collections.Generic.Dictionary`2[System.String,System.String] for Azure Tags?

My script using Get-AzDisk https://learn.microsoft.com/en-us/powershell/module/az.compute/get-azdisk?view=azps-5.9.0 like below
Get-AzDisk -ResourceGroupName $ResourceGroupName|Where-Object{$_.ManagedBy.Length -lt 1} | Select-Object Name, OsType, DiskSizeGB, ResourceGroupName, Location, Tags, Tier, ProvisioningState, TimeCreated | ogv
Cannot export the actual Tags values like in the Out-GridView does:
This is the .CSV column for Tags:
How to modify it so it can show the same data as in the OGV?
You could use a calculated property to format the object (which is a hashtable) to the desired output. Here is an example that uses an calculated property to serialize the tags as JSON:
Get-AzDisk -ResourceGroupName $ResourceGroupName
| Where-Object { $_.ManagedBy.Length -lt 1 }
| Select-Object Name, OsType, DiskSizeGB, ResourceGroupName, Location, #{e = {$_.Tags | ConvertTo-Json | Out-String}; l='Tags'} , Tier, ProvisioningState, TimeCreated
And here the corresponding output as CSV (after I pipe it to ConvertTo-CSV cmdlet):
Tags are a Hash.
$myTags = $vm.Tags
if($myTags) {
foreach ($key in $myTags.Keys) {
$curVal = $myTags[$key]
}
}
You can grab all the Keys and Values and write them to a string in the column.
Be aware the $curVal could itself be a multi-value system.collections.generic.dictionary and might require further manipulation.

Unable to update Azure Network Security Group using az cli

I am attempting to update a Network Security Group (SourceAddressPrefixes) via the cli. To begin, it appears that the az cmdlet Set-AzNetworkSecurityRuleConfig is broke; the output from the command states that it's succeeded but no change actually occurs to the NSG. Others have complained about this but no fix from MSFT yet.
With that said, I have tried a workaround method which saves the NSG in a variable, sets the SourceAddressPrefixes, and updates the NSG after:
$SaContext = (Get-AzStorageAccount -ResourceGroupName $RGName -Name $SAName).Context
$table = (Get-AzStorageTable -Name $TableName -Context $SaContext).CloudTable
$IPs = (Get-AzTableRow -Table $table).IP
$IPs = '"{0}"' -f ($IPs -join '","') # Updates the IPs to be double-quoted and separated by commas
$NSG = Get-AzNetworkSecurityGroup -ResourceGroupName $MyResourceGroup -Name $NSGName
($nsg.SecurityRules | Where-Object {$_.Name -eq 'HTTPS'}).SourceAddressPrefix = $IPList
$NSG | Set-AzNetworkSecurityGroup | Get-AzNetworkSecurityRuleConfig -Name $RuleName | Format-Table -AutoSize
The issue with the code above is that the cmdlet Set-AzNetworkSecurityGroup will not except value type system.string. It will only accept System.Collections.Generic.List[System.String]. Due to that I perform the following:
$IPList = New-Object System.Collections.Generic.List[string]
$IPList.Add($IPs)
Now, the previous Set-AzNetworkSecurityGroup accepts the array but now the command fails because the array values aren't double-quoted with a comma to separate them. Not sure what to do at this point.
Actual error messages below:
Cannot convert the (ip addresses here) value of type [system.string] to type "Systems.Collections.Generic.IList[System.String]
And once I convert my variable(array) to match that requirement the error is:
nsgRule has invalid Address Prefix. Value Provided (ip addresses here) statuscode:400 which i'm sure is because converting the array removes the double-quotes & commas.
If you want to update the Source Address Prefix of one Network Security Group rule, its value should be like
192.162.0.1
192.162.1.1
...
For example
$nsg= Get-AzNetworkSecurityGroup -Name $NSGName -ResourceGroupName $MyResourceGroup
$IPList = New-Object System.Collections.Generic.List[string]
$IPList.Add("192.162.0.1")
$IPList.Add("192.162.1.1")
($nsg.SecurityRules | Where-Object {$_.Name -eq 'Port_8080'}).SourceAddressPrefix =$IPList
$nsg|Set-AzNetworkSecurityGroup | Get-AzNetworkSecurityRuleConfig -Name "Port_8080" | Format-Table -AutoSize

Resources