Stop multiple Azure VMs at the same time using Azure PowerShell - azure

Get-AzureRmVM -ResourceGroupName RG-VNETS |
ForEach-Object {
Get-AzureRmVM -ResourceGroupName RG-VNETS -Name $_.Name -Status
} |
ForEach-Object {
if (-Not ($_.Statuses[1].DisplayStatus -like "*deallocated*")) {
Stop-AzureRmVM -ResourceGroupName RG-VNETS -Name $_.Name -Force
}
}
I've got this script that stops all my Azure VMs, the catch here is that this script shuts down one VM at a time.
i.e. if I have three VMs: VM1, VM2, VM3
The script doesn't shut down VM2 until VM1 is fully shutdown and so on. I don't know if there's a way to tell PowerShell not to wait for each VM to be fully shutdown to proceed with the following one.

There is already a feature request on GitHub for doing such operations asynchronously which should be implemented in the near future.
In the meantime you could do a workaround like the following using the PoshRSJob module - just replace temp4so with your resource group name
# Install PoshRSJob if necessary
#
# Install-Module PoshRSJob
Login-AzureRmAccount
$start = Get-Date
$jobs = Get-AzureRmVM -ResourceGroupName temp4so |
% {
Get-AzureRmVM -ResourceGroupName temp4so -Name $_.Name -Status
} |
% {
if (-Not ($_.Statuses[1].DisplayStatus -like "*deallocated*")) {
$vm = $_
Start-RSJob {
Stop-AzureRmVM -ResourceGroupName temp4so -Name ($using:vm).Name -Force
}
}
}
$jobs | Wait-RSJob | Receive-RSJob
$jobs | Remove-RSJob
$end = Get-Date
Write-Host ("Stopping took {0}" -f ($end - $start))
which in my test case with 3 VMs resulted in output similar to the following which shows that the operations where done in parallel
OperationId :
Status : Succeeded
StartTime : 24.09.2016 18:49:10
EndTime : 24.09.2016 18:51:32
Error :
OperationId :
Status : Succeeded
StartTime : 24.09.2016 18:49:11
EndTime : 24.09.2016 18:51:22
Error :
OperationId :
Status : Succeeded
StartTime : 24.09.2016 18:49:11
EndTime : 24.09.2016 18:51:22
Error :
Stopping took 00:02:32.9115538
Note: You cannot simply use the standard Start-Job to offload the sync. operations to a background job as the newly created PowerShell instances in the background do not share the context with your initial session and therefore would require you to authenticate again for each of those sessions. As PoshRSJob uses PowerShell runspaces within the initial PowerShell instance it does not require to authenticate again.

They implemented -AsJob per the GitHub feature request DAXaholic linked. Here's an example taken from the GitHub comments section.
$VMList = Get-AzureRmVM -ResourceGroupName $resourceGroupName
$JobList = #()
foreach ($vm in $VMList) {
$JobList += Stop-AzureRmVm -ResourceGroupName $resourceGroupName -Name $vm -Force -AsJob | Add-Member -MemberType NoteProperty -Name VMName -Value $vm.Name -PassThru
}
and if you want to wait for the jobs to finish with cleanup:
$JobList | Wait-Job | Receive-Job
$JobList | Remove-Job

Related

Powershell script to get list of Running VM's and stop them

Am using this script, but its gathering all the vms and stopping it one by one even when the VM is already in stopped state
$vm = Get-Azvm
foreach($vms in $vm)
{
$resource = Get-Azvm | where {$_.Statuses -eq "Running"}
if($resource -ne $null)
{
Write-Output "Stopping virtual machine..." + $vms
Stop-AzVM -ResourceGroupName $resource.ResourceGroupName -Name $vms -Force
}
else
{
Write-output "Virtual machine not found:" + $vms
}
}
Based on the above shared requirement , we have modified the PowerShell script to check the virtual machines status ( whether it is running or not ), if virtual Machine is running you need to stop it using stop-Azvm cmdlet.
Checked the below script(while testing we passed resource group flag to the Get-Azvm ) in our local environment which is working fine.
$vm = Get-Azvm -Status
foreach($vms in $vm)
{
$statuscheck = Get-AzVM -ResourceGroupName $vms.ResourceGroupName -Name $vms.Name -Status
if($statuscheck.Statuses.DisplayStatus[1] -eq "VM running")
{
Write-Output "Stopping virtual machine...$($vms.Name)"
Stop-AzVM -ResourceGroupName $vms.ResourceGroupName -Name $vms.Name -Force
}
else
{
Write-output "Virtual machine $($vms.Name) is already in stopped state"
}
}
Here is the sample output for reference:

Get running VMs on Azure

I have the below code to check daily if all the VMs that are meant to auto-start have started successfully.
$Date = (Get-Date -Format "dd-MM-yyyy HH-mm").toString()
$Results = "C:\temp\DailyChecks_$($Date).txt"
$Cred = get-credential -UserName 'AZ-User'
Connect-AzAccount -Tenant 'TenantID' -credential $Cred | out-null
$fCount = (Get-AzVM -Status | Where-Object { $_.tags['Managed By'] -like 'Manager' }).count
do {
$rCount = (Get-AzVM -Status | Where-Object { $_.tags['Managed By'] -like 'Manager' -and $_.PowerState -eq 'VM Running' }).count
if($rCount -lt $fCount)
{
write-host "There are $rCount VMs running, checking again"
}
elseif($rCount -eq $fCount){write-host "There are $rCount VMs running, exiting loop"}
}until($fCount -eq $rCount)
Get-AzVM -Status | Where-Object { $_.tags['Managed By'] -like 'Manager' } |
Select-Object Name, PowerState | Format-Table | out-file $Results
I have a couple of questions:
Is there a better way to write this code?
How can I check if the code has been running for longer than 30 minutes and yet not all VMs are running?
You can determine for how much the time the script is running by using a StopWatch object.
$sw = [System.Diagnostics.Stopwatch]::new()
$sw.Start()
Whenever you want, just check the elapsed time by checking the elapsed property
$sw.Elapsed
$sw.Elapsed.TotalSeconds
Write-Host "Time elapsed: $([Math]::Round($sw.Elapsed.TotalMinutes,0)) min $($sw.Elapsed.Seconds) seconds"

How to get the list of azure servers having Auto-Shutdown disabled using PowerShell?

I want to get the list of azure servers having auto-shutdown disabled on them, I have the below script but the issue with the script is that it gets the list of RG's under the Subscription GUID but repeats the output after every loop.
Import-AzureRmContext -Path "$PSScriptRoot\AzureProfile.json"
Select-AzureRmSubscription -SubscriptionId {subscriptionId}
[array]$ResourceGroupArray = Get-AzureRMVm | Select-Object -Property ResourceGroupName, Name, VmId
foreach ($resourceGroup in $ResourceGroupArray){
$targetResourceId = (Get-AzureRmVM -ResourceGroupName $resourcegroup.ResourceGroupName -Name $resourceGroup.Name).Id
$shutdownInformation = (Get-AzureRmResource -ResourceGroupName $resourcegroup.ResourceGroupName -ResourceType Microsoft.DevTestLab/schedules -Expandproperties).Properties
Write-Host "ID: " $targetResourceId
$shutdownInformation
The output for each VM is displayed in the following format,
What I want is simple, I want the VM name and its status of Auto-shutdown to be displayed on the screen so that its easy for me to find out which all VM have auto-shutdown currently disabled on them.
Any help related to this would be helpful.
You just need to get the microsoft.devtestlab/schedules resource ID using:
/subscriptions/{subscriptionId}/resourceGroups/{rgName}/providers/microsoft.devtestlab/schedules/shutdown-computevm-{vmName}
Then iterate over all your VMs using Get-AzVM, Get the microsoft.devtestlab/schedules resource using Get-AzResource, then output VM name and status into a table using Format-Table.
$subscriptionId = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
Set-AzContext -SubscriptionId $subscriptionId
& {
foreach ($vm in Get-AzVM) {
try {
$shutdownResource = Get-AzResource `
-ResourceId "/subscriptions/$subscriptionId/resourceGroups/$($vm.ResourceGroupName)/providers/microsoft.devtestlab/schedules/shutdown-computevm-$($vm.Name)" `
-ErrorAction Stop
[PSCustomObject]#{
VMName = $vm.Name
ShutdownStatus = $shutdownResource.Properties.status
}
}
catch {
[PSCustomObject]#{
VMName = $vm.Name
ShutdownStatus = $_.Exception.Message
}
}
}
} | Format-Table -AutoSize
To set the context to the correct subscription, we can use Set-AzContext.
The above however is using the latest Az modules. You can do the same using the equivalent AzureRm modules.
$subscriptionId = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
Set-AzureRmContext -SubscriptionId $subscriptionId
& {
foreach ($vm in Get-AzureRmVM) {
try {
$shutdownResource = Get-AzureRmResource `
-ResourceId "/subscriptions/$subscriptionId/resourceGroups/$($vm.ResourceGroupName)/providers/microsoft.devtestlab/schedules/shutdown-computevm-$($vm.Name)" `
-ErrorAction Stop
[PSCustomObject]#{
VMName = $vm.Name
ShutdownStatus = $shutdownResource.Properties.status
}
}
catch {
[PSCustomObject]#{
VMName = $vm.Name
ShutdownStatus = $_.Exception.Message
}
}
}
} | Format-Table -AutoSize
Although I do recommend moving to the Az module since support for AzureRm is ending December 2020. You can read the documentation for more information about this.
The above code should give you an output similar to the following
VMName ShutdownStatus
------ --------------
vm1 Enabled
vm2 Disabled
Update
The Call operator & is used here to run the for loop as a script block. You can read more about this in about_Script_Blocks.
Try something like this to get the auto-shutdown status of all VMs. Instead of trying to get the schedules inside the loop, get all the ones in the subscription and match them based on the VM's full resource Id.
[array]$VMArray = Get-AzureRMVm | Select-Object -Property ResourceGroupName, Name, VmId, Id
$ShutdownInformation = (Get-AzureRmResource -ResourceType Microsoft.DevTestLab/schedules -Expandproperties).Properties
foreach($vm in $VMArray) {
$ShutdownStatus = "Not Configured"
$Schedule = $ShutdownInformation | Where-Object { $_.targetResourceId -eq $vm.Id } | Select -First 1
if($Schedule -ne $null) {
$ShutdownStatus = $Schedule.status
}
Write-Host $vm.VmId $ShutdownStatus
}

Azure Automation Runbook missing mandatory parameters

I'm trying to set a Tag on all virtual machines in my subscription but I keep getting errors when running the Runbook.
The error is the following:
Get-AzureRmVM : Cannot process command because of one or more missing mandatory parameters: ResourceGroupName. At line:30
Here is my Runbook:
$azureConnection = Get-AutomationConnection -Name 'AzureRunAsConnection'
#Authenticate
try {
Clear-Variable -Name params -Force -ErrorAction Ignore
$params = #{
ServicePrincipal = $true
Tenant = $azureConnection.TenantID
ApplicationId = $azureConnection.ApplicationID
CertificateThumbprint = $azureConnection.CertificateThumbprint
}
$null = Add-AzureRmAccount #params
}
catch {
$errorMessage = $_
Throw "Unable to authenticate with error: $errorMessage"
}
# Discovery of all Azure VM's in the current subscription.
$azurevms = Get-AzureRmVM | Select-Object -ExpandProperty Name
Write-Host "Discovering Azure VM's in the following subscription $SubscriptionID Please hold...."
Write-Host "The following VM's have been discovered in subscription $SubscriptionID"
$azurevms
foreach ($azurevm in $azurevms) {
Write-Host "Checking for tag $vmtagname on $azurevm"
$tagRGname = Get-AzureRmVM -Name $azurevm | Select-Object -ExpandProperty ResourceGroupName
$tags = (Get-AzureRmResource -ResourceGroupName $tagRGname -Name $azurevm).Tags
If ($tags.UpdateWindow){
Write-Host "$azurevm already has the tag $vmtagname."
}
else
{
Write-Host "Creating Tag $vmtagname and Value $tagvalue for $azurevm"
$tags.Add($vmtagname,$tagvalue)
Set-AzureRmResource -ResourceGroupName $tagRGname -ResourceName $azurevm -ResourceType Microsoft.Compute/virtualMachines -Tag $tags -Force `
}
}
Write-Host "All tagging is done"
I tried importing the right modules but this doesn't seem to affect the outcome.
Running the same commands in Cloud Shell does work correctly.
I can reproduce your issue, the error was caused by this part Get-AzureRmVM -Name $azurevm, when running this command, the -ResourceGroupName is needed.
You need to use the Az command Get-AzVM -Name $azurevm, it will work.
Running the same commands in Cloud Shell does work correctly.
In Cloud shell, azure essentially uses the new Az module to run your command, you can understand it runs the Enable-AzureRmAlias before the command, you could check that via debug mode.
Get-AzureRmVM -Name joyWindowsVM -debug
To solve your issue completely, I recommend you to use the new Az module, because the AzureRM module was deprecated and will not be updated.
Please follow the steps below.
1.Navigate to your automation account in the portal -> Modules, check if you have imported the modules Az.Accounts, Az.Compute, Az.Resources, if not, go to Browse Gallery -> search and import them.
2.After import successfully, change your script to the one like below, then it should work fine.
$azureConnection = Get-AutomationConnection -Name 'AzureRunAsConnection'
#Authenticate
try {
Clear-Variable -Name params -Force -ErrorAction Ignore
$params = #{
ServicePrincipal = $true
Tenant = $azureConnection.TenantID
ApplicationId = $azureConnection.ApplicationID
CertificateThumbprint = $azureConnection.CertificateThumbprint
}
$null = Connect-AzAccount #params
}
catch {
$errorMessage = $_
Throw "Unable to authenticate with error: $errorMessage"
}
# Discovery of all Azure VM's in the current subscription.
$azurevms = Get-AzVM | Select-Object -ExpandProperty Name
Write-Host "Discovering Azure VM's in the following subscription $SubscriptionID Please hold...."
Write-Host "The following VM's have been discovered in subscription $SubscriptionID"
$azurevms
foreach ($azurevm in $azurevms) {
Write-Host "Checking for tag $vmtagname on $azurevm"
$tagRGname = Get-AzVM -Name $azurevm | Select-Object -ExpandProperty ResourceGroupName
$tags = (Get-AzResource -ResourceGroupName $tagRGname -Name $azurevm).Tags
If ($tags.UpdateWindow){
Write-Host "$azurevm already has the tag $vmtagname."
}
else
{
Write-Host "Creating Tag $vmtagname and Value $tagvalue for $azurevm"
$tags.Add($vmtagname,$tagvalue)
Set-AzResource -ResourceGroupName $tagRGname -ResourceName $azurevm -ResourceType Microsoft.Compute/virtualMachines -Tag $tags -Force `
}
}
Write-Host "All tagging is done"

Problem with PSCustomObject data collection

I'm preparing a table with information about VM name and provision date of OS disk. I can easily retrieve that information from $VM.disks.statuses.time[0] command, if individual VM is assigned to $VM, but I when i try to collect data into table, I got an error:
Cannot index into a null array.
At line:4 char:1
+ [PSCustomObject]#{
+ ~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : NullArray
This is my code:
$VMs = Get-AzVM -status
$vmOutput = $VMs | ForEach-Object {
[PSCustomObject]#{
"VM Name" = $_.Name
"Provision Date" = $_.disks.statuses.time[0].ToString()
}
}
I can reproduce your issue, the issue was caused by the outputs of Get-AzVM -status and Get-AzVM -ResourceGroupName <ResourceGroupName> -Name <Name> -Status are different.
The output of Get-AzVM -status will not has the disks property, but when you get individual VM status via Get-AzVM -ResourceGroupName <ResourceGroupName> -Name <Name> -Status, it will have the property, so you got the error.
Get-AzVM -status:
Get-AzVM -ResourceGroupName <ResourceGroupName> -Name <Name> -Status:
Solution:
To fix the issue, just to use Get-AzVM -ResourceGroupName <ResourceGroupName> -Name <Name> -Status in your script.
$VMs = Get-AzVM -status
$vmOutput = $VMs | ForEach-Object {
$VMstatus = Get-AzVM -ResourceGroupName $_.ResourceGroupName -Name $_.Name -Status
[PSCustomObject]#{
"VM Name" = $VMstatus.Name
"Provision Date" = $VMstatus.disks.statuses.time[0].ToString()
}
}
You can use the Get-AzDisk command to retrieve disk creation information.
$disks = Get-AzDisk | Where-Object { $_.Managedby }
$vmOutput = foreach ($disk in $disks) {
[pscustomobject]#{"VM Name" = ($disk.ManagedBy -split "/")[-1]
"Provisioned Date" = $disk.TimeCreated
}
}
The error says:
Cannot index into a null array.
So my guess is that something here $_.disks.statuses.time[0].ToString() is $null. Therefore you should add some $nullchecks:
$VMs = Get-AzVM -status
$vmOutput = $VMs | ForEach-Object {
if ($_.disks -and $_.disks.statuses -and $_.disks.statuses.time -and ($_.disks.statuses.time.Count -gt 0)){
[PSCustomObject]#{
"VM Name" = $_.Name
"Provision Date" = $_.disks.statuses.time[0].ToString()
}
}
}
Hope that helps.

Resources