I am trying to write a PowerShell script that will look for shutdown VMs in my Resource Group and deallocate them. The output of the below script does not give me the VM name "clean" when I attempt tp assign the below as a variable. The end result is to execute the Stop-AzureRmVM -ResourceGroupName LAB -Name $VM -force
So for more context, lets say AVGJOE-DC1 is in a stopped state and I run the below line in Azure Powershell it will display
Name
----
AVGJOE-DC1
If I then if I tried to use $VM to call AVGJOE-DC1 in the
Stop-AzureRmVM -ResourceGroupName LAB -Name $VM -force
it fails due to the variable being set to a longer string something like
MicroSoftComputerResource\Resourcegroup[#Name=AVGJOE-DC1].
Hopefully that makes sense.
$VM = Get-AzureRmVM -ResourceGroupName LAB | get-azurermvm -Status | ?{$_.statuses.displaystatus -eq "VM stopped"} | select name
Just like #Theo said in the comment, select name gives you an object with property name. If you want the string value of the name property, you can use Select-Object -ExpandProperty name instead of select name.
Related
I'm trying to parallelize a portion of a bigger PWSH script, basically I need to cycle through a list of VMs and execute some commands on them, and to speed up the process, I need this to be executed in parallel:
#Utils.ps1 contains all the functions
.(".\src\utils.ps1")
# List of VMs on which we execute the space reclaim
$VMs = Get-Content .\vmnames.txt
# Check if directory ./Results exists, if not it will be created
CheckDirResults
# Check if dir temp exists, if not it will be created
CheckDirTemp
# Asks for vCenter credentials
GetVServerCred
# Asks for VM credentials
GetVMCred
# Connects to vCenter
ConnectVcenter
# Commands that will be executed on VMs on line 94 (Invoke-VMScript)
$InvokeScript = #'
#! /bin/bash
pwd
'#
foreach ($vm in $VMs) {
$vms = Get-VM -Name $vm
$vms | ForEach-Object -Parallel {
Write-Output "Testing on $_"
Invoke-VMScript -VM $_ -GuestCredential $VMCred -ScriptText $InvokeScript -Confirm:$false
}
}
I also tried to simply execute the Invoke directly on $VMs like this:
$VMs | ForEach-Object -Parallel {
Write-Output "Testing on $_"
Invoke-VMScript -VM $_ -GuestCredential $VMCred -ScriptText $commands
}
In both cases, the Invoke-VMScript can't either connect to the VMs or can't find the chunk of code to execute on the VMs.
Errors:
"test.ps1" 44L, 934C 36,0-1 81%
---- ---- ----
evl6800756.sys.ntt.eu 443 VSPHERE.LOCAL\Administrator
Testing on euczdrpoc03
Testing on euczdrpoc30
Invoke-VMScript:
Line |
3 | Invoke-VMScript -VM $_ -GuestCredential $VMCred -ScriptText $comma …
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| 1/17/2023 10:42:02 AM Invoke-VMScript You are not currently connected to any servers. Please connect first using a Connect cmdlet.
Invoke-VMScript:
Line |
3 | Invoke-VMScript -VM $_ -GuestCredential $VMCred -ScriptText $comma …
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| 1/17/2023 10:42:02 AM Invoke-VMScript Value cannot be found for the mandatory parameter ScriptText
Invoke-VMScript:
Line |
3 | Invoke-VMScript -VM $_ -GuestCredential $VMCred -ScriptText $comma …
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| 1/17/2023 10:42:02 AM Invoke-VMScript You are not currently connected to any servers. Please connect first using a Connect cmdlet.
Invoke-VMScript:
Line |
3 | Invoke-VMScript -VM $_ -GuestCredential $VMCred -ScriptText $comma …
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| 1/17/2023 10:42:02 AM Invoke-VMScript Value cannot be found for the mandatory parameter ScriptText
The $using: scope modifier inside a -Parallel script-block lets you refer to variables assigned a value outside that script-block. Examples in your question are not complete enough to say for certain, but hopefully this explains at least some of the difficulty.
So for example within the parallel block, try $using:commands instead of your $commands. This bit from reference doc could be more helpfully spelled out, IMHO.
Use the $using: keyword to pass variable references to the running
script.
That's demonstrated more clearly in one of the reference examples, in this blog post, and this answer.
Edit: sounds like the parallel runspaces do not inherit the vCenter connection. The open proposal for a -UseCurrentState switch to transfer the current runspace state to -Parallel loop iterations might help in your situation. Meanwhile could try passing -Server $using:DefaultVIServer to relevant commands inside the parallel block. LucD's comment that PowerCLI is not threadsafe may also be worth researching alongside the note in the reference doc:
The ForEach-Object -Parallel parameter set runs script blocks in parallel on separate process threads. The $using: keyword allows passing variable references from the cmdlet invocation thread to each running script block thread. Since the script blocks run in different threads, the object variables passed by reference must be used safely. Generally it is safe to read from referenced objects that don't change. But if the object state is being modified then you must used thread safe objects, such as .NET System.Collection.Concurrent types
I managed to do it this way:
# Commands that will be executed on VMs on line 94 (Invoke-VMScript)
$commands = #'
#! /bin/bash
hostname
'#
# Loads the VMs names from the file vmnames.txt
$vms = Get-Content ".\vmnames.txt"
# Uses the content of the file vmnames.txt to get the VM objects
$vmnames = Get-VM -Name $vms
# Executes the commands on the VMs
$vmnames | ForEach-Object -Parallel {
# Loads the variables in the parallel session
$commands = $using:commands
$VMCred = $using:VMCred
Invoke-VMScript -VM $_ -GuestCredential $VMCred -ScriptText $commands
}
For some reasons, piping directly $vms (so the content of the file to ForEach-Object -Parallel won't pass correctly the value (in this case the VM name), while using the Get-VM -Name $vms will do.
Then, once this is solved the ForEach-Object -Parallel will finally get the correct VM name assigned to $_.
I'm currently studying for az-104 azure exam and going through some scripting exercises.
I can confirm the script below works, but I don't understand how...
in the last few lines of the script the variable $vm is defined twice. how is this possible?
also when you define a variable is it actually running the commands that are being defined? i didn't realize that was the case, but it definitely seems to be.. can someone please explain?
$resourcegroup = "2019dc_group"
$machinename = "2019dc"
$location = "east us"
$storagetype = "Standard_LRS"
$name = "newdisk"
$size = 20
$datadiskconfig = new-azdiskconfig -SkuName $storagetype -location $location -createoption empty -DiskSizeGB $size
$datadisk01 = new-azdisk -diskname $name -disk $datadiskconfig -ResourceGroupName $resourcegroup
$vm = Get-AzVM -name $machinename -resourcegroupname $resourcegroup
$vm = add-azvmdatadisk -vm $vm -name $name -createoption attach -ManagedDiskId $datadisk01.id -lun 1
update-azvm -vm $vm -resourcegroupname $resourcegroup
Variable types in PowerShell are dynamic. They are automatically "adjusted" to the type of the object they are assigned to.
Yes, the commands are being executed first and then the object is placed in the variable. This is also why you are able to use the vm variable in the command and assign the result back to that variable. When the "add-azvmdatadisk" command is executed, the type of the variable is still an Azure VM. When the assignment takes places, it is an Azure VM Data Disk
You can use Get-Member to get the type, methods, properties, events etc. for any object.
$a = 1 #System.Int32
$a | Get-Member
$a = "1" #System.String
$a | Get-Member
$a = Get-Service | Select-Object -First 1 #System.ServiceProcess.ServiceController
$a | Get-Member
The $VM variable is not defined twice. It's being assigned a value twice. One doesn't need to define variables in Powershell, assignment will automatically create one.
Let's review the code. First off,
$vm = Get-AzVM -name $machinename -resourcegroupname $resourcegroup
After the Get-AzVM, you'll have its output in $vm. Nothing fancy here. Note that before this statement, $vm didn't exist, and trying to work with it would generate an error about using null value.
$vm = add-azvmdatadisk -vm $vm -name $name -createoption attach -ManagedDiskId $datadisk01.id -lun 1
Here $vm is used twice. The first usage is when you pass it as an argument to Add-AzVmDataDisk. The cmdlet returns a PSVirtualMachine object, so after adding the disk, you'd get an updated version of $vm into the variable. This might or might not be important a difference. If the Azure VM objects are lazy evaluated, the old $vm would contain hardware information without the new disk. This often is the case, as it improves performance. By assigning the output of the later cmdlet into $vm, you are sure to have up-to-date version of your VM.
If you have a lab environment, try printing $vm before the last disk addition cmdlet call and after it. See what's different on the outputs.
Im trying to write a powershell query to return the following table:
NAME STATUS RESOURCE GROUP VMSIZE SIZEREASON (TAG) LOCATION PUBLIC DNS NAME
I can use Get-AzureRmVM | Select-Object Name, ResourceGroupName, Location but when I try to add on VmSize, the column is there but without values.
If I run just Get-AzureRmVM then I get the VmSize, so I don't understand why I can't select it.
I also need to work out how to add in Status, SIZEREASON (TAG),PUBLIC DNS NAME
Any guidence is most appreciated.
Its also worth noting, that my table also needs to contain both Resource Managed VM's and Classic VM's (So all VM's)
UPDATE
My Powershell query is currently:
Get-AzureRmVm `
| Select-Object ResourceGroupName `
, Name `
, Location `
, #{Name="VmSize"; Expression={$_.ToPSVirtualMachine().HardwareProfile.VmSize}} `
, #{Name="SizeReason(Tag)"; Expression={$_.ToPSVirtualMachine().Tags.Values}} `
, FullyQualifiedDomainName `
| Format-Table
The outstanding problems are:
FullyQualifiedDomainName does not work
I am also missing the status (Eg: Running, Stopped (Deallocated) etc..)
This was trickier than I expected....
Get-AzureRmVm | Select-Object #{Name="VmSize"; Expression={$_.ToPSVirtualMachine().HardwareProfile.VmSize}}
I have a powershell script to deploy a service bus namespace
This is the command i use to deploy it .
New-AzureRmServiceBusNamespace -ResourceGroup $ResourceGroupName -NamespaceName $ServiceBusNamespace -Location $Location
The above command expects a location parameter to be entered. I want that location value as the Location where my resource group is located . How do i extract the Location value of my resource Group ?
I tried various methods and failed. it shows #{location=west us} , when i tried this
$location = Set-AzureRmResourceGroup -Name $ResourceGroupName -Tag #{} | Select-Object Location
Write-Host $location
Need help . Thanks !
$loc = Get-AzureRMResourceGroup -Name $ResourceGroupName | select-object -expandproperty location
Well, if you want to GET something use appropriate verb.
or like this:
$rg = Get-AzureRMResourceGroup -Name $ResourceGroupName
$rg.location
The below code works with Az powershell module.
$loc= Get-AzResourceGroup -Name $resourceGroupName #Gets the resource group details and stores in $loc variable
$location= $loc.location #Gets the location name from resource group properties and stores in $location variable
Write-Host $location #outputs the resource group location
I am using the command:
$hostnames = Get-AzureRmWebApp -ResourceGroupName "app-rg" -Name "app" |
Select-Object -Property hostnames
To return the list of hostnames with an azure web app as below:
HostNames : {a.arup.com, a.internet.trafficmanager.net, a.azurewebsites.net}
However, I am interested in getting the first domain (a.arup.com). How would this be done with powershell?
There is always an alternative in PowerShell:
$hostnames = Get-AzureRmWebApp -ResourceGroupName "app-rg" -Name "app" |
Select-Object -ExpandProperty hostnames -First 1
In powershell, when you reference properties of an item in a list, you interact with them as a list. So you can simply do the following:
$hostnames = (Get-AzureRmWebApp).HostNames
$hostnames[0]
To get the first one.