foreach loop issues in Azure Powershell Runbook - powershell

I have following code to loop all my vms in all subscriptions but for some reason Runbook never goes to my loop and therefore does nothing. This managed identity has VM contributors role from the root so permission is not the issue right now.
# Ensures you do not inherit an AzContext in your runbook
Disable-AzContextAutosave -Scope Process | Out-Null
Write-Output "Connecting to azure via Connect-AzAccount -Identity"
Connect-AzAccount -Identity
Write-Output "Successfully connected with Automation account's Managed Identity"
Set-AzContext -SubscriptionId "0000-xxxx"
$Subscriptions = Get-AzSubscription
foreach ($sub in $Subscriptions) {
# suppress output on this line
Write-Host Working with Subscription $sub.Name
$null = Get-AzSubscription -SubscriptionName $sub.Name | Set-AzContext
$vms = Get-AzVM
foreach ($vm in $vms) {
Write-Host Working with virtual Machine $vm.Name in resource group $vm.ResourceGroupName
stop-azvm -Name $vm.Name -ResourceGroup $vm.ResourceGroupName
}
}
If I put Get-AzVM or Get-AzSubscription command before loop it will show all my subscriptions or vms that are in context subscriptions so I know the commands work.
Script usually run like 5 minutes and then just goes to complied without doing really anything.
If I run the code from my computer in Visual Studio Code it goes fine trough but now am I missing now.

Related

How to run Start-AzureVM in parallel from a VSTS Build in Azure Powershell?

I have a PowerShell script running in an Azure Powershell task in VSTS, that starts several Azure virtual machines in a DevTest lab, but starts the domain controller first, waits for it to start, and then starts the others one after the other.
I'd like to start all the others in parallel and I tried to do this using Start-Job but that spawns a new Powershell process which doesn't have the security context of the Azure login, so it fails. I'm trying something like this:
[cmdletbinding()]
param (
[ValidateSet("Start","Stop")][string]$Action,
$labName = "DevTestLab",
$labResourceGroup = "DevTestLabRG"
)
if ($Action -eq "Start") {
Write-Verbose "Starting the domain controller first"
Get-AzureRmResource | Where-Object {
$_.Name -match "dc" -and
$_.ResourceType -eq "Microsoft.Compute/virtualMachines" -and
$_.ResourceGroupName -match $labResourceGroup } | Start-AzureRmVM
Write-Verbose "Starting other machines in the lab as background jobs"
foreach ($AzureRMResource in Get-AzureRmResource |
Where-Object {
$_.Name -notmatch "dc" -and
$_.ResourceType -eq "Microsoft.Compute/virtualMachines" -and
$_.ResourceGroupName -match $labResourceGroup } )
{
Start-Job {
$myResource = $using:AzureRMResource
Start-AzureRMVM -Name $myResource.Name -ResourceGroupName $myResource.ResourceGroupName
}
}
# wait for all machines to start before exiting the session
Get-Job | Wait-Job
Get-Job | Remove-Job
}
I am using a Hosted Agent to run the script, so I can't have many agents running at once, and VSTS doesn't support parallel tasks as far as I am aware.
Any ideas on how to solve this?
Instead of creating a PowerShell script you can also use the Azure CLI. The Azure CLI has a vm start and vm stop command that takes a list of ids. You can also use the CLI to query for all the ids you want. The following is a simple Bash snippet that starts/stops all vms where the id contains Test.
# example usage
az vm start --ids $(
az vm list --query "[].id"
-o tsv | grep "Test"
)
az vm stop --ids $(
az vm list --query "[].id"
-o tsv | grep "Test"
)
Source: Azure CLI 2.0: Quickly Start / Stop ALL VMs
You also can try it with az vm start command with --no-wait parameter through Azure CLI task.

Azure Powershell how to get running services of a VM via Runbook?

I'm trying to write an Azure Powershell Runbook that will start a VM, and then check if a windows service on the VM is running or not and start it.
I can get the VM started, but enumerating the services isn't working. I'm brand new on Azure Runbooks so I could be doing something wrong. I limited the below code to only the Get-Service bit and not the VM starting.
# Returns strings with status messages
[OutputType([String])]
param (
[Parameter(Mandatory=$false)]
[String] $AzureConnectionAssetName = "AzureRunAsConnection",
[Parameter(Mandatory=$false)]
[String] $ResourceGroupName = ""
)
try {
# Connect to Azure using service principal auth
$ServicePrincipalConnection = Get-AutomationConnection -Name $AzureConnectionAssetName
Write-Output "Logging in to Azure..."
$Null = Add-AzureRmAccount `
-ServicePrincipal `
-TenantId $ServicePrincipalConnection.TenantId `
-ApplicationId $ServicePrincipalConnection.ApplicationId `
-CertificateThumbprint $ServicePrincipalConnection.CertificateThumbprint
}
catch {
if(!$ServicePrincipalConnection) {
throw "Connection $AzureConnectionAssetName not found."
}
else {
throw $_.Exception
}
}
# If there is a specific resource group, then get all VMs in the resource group,
# otherwise get all VMs in the subscription.
if ($ResourceGroupName) {
$VMs = Get-AzureRmVM -ResourceGroupName $ResourceGroupName
}
else {
$VMs = Get-AzureRmVM
}
# Try and enumerate the VM's services
foreach ($VM in $VMs) {
Write-Output "Listing all services..."
Write-Output ("VM: {0}" -f $VM.Name)
$VM | Get-Service | Format-Table -AutoSize
Write-Output "Listing alternative method..."
Get-Service -ComputerName $VM.Name | Format-Table -AutoSize
Write-Output "Finished listing..."
}
And the output is this:
Logging in to Azure...
Listing all services...
VM: demo-0
Listing alternative method...
Finished listing...
Well, first of all, Starting VM is asynchronous, so you need to wait for the VM to actually boot, and the Get-Service wouldn't work anyway, as to get the services from a VM you need to authenticate against that VM, so either user PSsessions or invoke-command, something like that. Just look on how to remote into servers with powershell or how to issue powershell command to remote PC's. This case is nothing different. And it has nothing to do with how Azure Automation works.
When you are running Azure Automation runbooks, you have the choice of the azure queue or creating a hybrid worker. The azure queue is good for many processes, but it will not have access to the VMs directly to run commands such as get-service.
To expand on #4c74356b41 answer, you will need to use remote powershell to execute the command (using New-PSSession) but you will also have to ensure that those commands are running on an Azure Automation Hybrid Worker
In the comments below, you asked about credentials. You can set credentials in the Azure Automation account and then have them used by your script when creating a new session. See this link
You could try to use the following cmdlets. It works for me.
# Try and enumerate the VM's services
foreach ($VM in $VMs) {
Write-Output "Listing all services..."
Write-Output ("VM: {0}" -f $VM.Name)
$ResourceGroupName=$VM.ResourceGroupName
$Name=$VM.Name
$status=(Get-AzureRmVM -ResourceGroupName $ResourceGroupName -Name $Name -Status).Statuses[1].code
if($status -like "PowerState/deallocated")
{
Write-Output "VM is stopped, starting VM"
if (Start-AzureRmVm -ResourceGroupName $ResourceGroupName -Name $Name)
{
Write-Output "Start VM successfuly"
}else
{
Write-Output "Start VM failed"
break
}
}
##get VM's Public IP
$nicName = ($VM.NetworkInterfaceIDs[0] -split '/')[-1]
$pip = (Get-AzureRmNetworkInterface -Name $nicName -ResourceGroupName $ResourceGroupName).IpConfigurations.publicipaddress.id
$PublicIP=(Get-AzureRmPublicIpAddress -ResourceGroupName shui -Name ($pip -split '/')[-1]).IpAddress
$Uri="http://$($PublicIP):5986"
Write-Output "Get ConnectionUri $Uri"
##get Credential from assets
$shui=Get-AutomationPSCredential -Name 'shui'
$session=New-PSSession -ConnectionUri $Uri -Credential $shui -SessionOption (New-PSSessionOption -SkipCACheck -SkipCNCheck -SkipRevocationCheck)
$result=Invoke-Command -Session $session -ScriptBlock {ipconfig}
Write-Output "$result"
Write-Output "Finished listing..."
}
Before you use this script. Firstly,you should open ports 5896 on your VM's firewall and NSG, you could check as the following link. Please ensure you could telnet IP 5986 on your local PC.
You should import AzureRM.Network modules to your Automation Account. More information about how to import Modules please refer to this link.
3.Store your VMs's passord to Runbook, you could refer to this link. When you want to use the credentials, you could use the script below:
##get Credential from assets
$shui=Get-AutomationPSCredential -Name 'shui'
$session=New-PSSession -ConnectionUri $Uri -Credential $shui -SessionOption (New-PSSessionOption -SkipCACheck -SkipCNCheck -SkipRevocationCheck)

Get job status in Azure Runbook

I would like to have a runbook run on a schedule but exit if the last job hasn't finished running (i.e. if the schedule is every 2 hours and a job takes 2.5 hours then the next job shouldn't run).
I've tried to use Get-AzureAutomationJob to get the last job status (https://azure.microsoft.com/en-gb/documentation/articles/automation-runbook-execution/#retrieving-job-status-using-windows-powershell), but I can't get it to work. I presume all the preamble to get the subscription etc is required.
"Get-AzureAutomationJob : The Automation account was not found."
$ConnectionAssetName = "AzureClassicRunAsConnection"
# Get the connection
$connection = Get-AutomationConnection -Name $connectionAssetName
# Authenticate to Azure with certificate
Write-Verbose "Get connection asset: $ConnectionAssetName" -Verbose
$Conn = Get-AutomationConnection -Name $ConnectionAssetName
if ($Conn -eq $null)
{
throw "Could not retrieve connection asset: $ConnectionAssetName. Assure that this asset exists in the Automation account."
}
$CertificateAssetName = $Conn.CertificateAssetName
Write-Verbose "Getting the certificate: $CertificateAssetName" -Verbose
$AzureCert = Get-AutomationCertificate -Name $CertificateAssetName
if ($AzureCert -eq $null)
{
throw "Could not retrieve certificate asset: $CertificateAssetName. Assure that this asset exists in the Automation account."
}
Write-Verbose "Authenticating to Azure with certificate." -Verbose
Set-AzureSubscription -SubscriptionName $Conn.SubscriptionName - SubscriptionId $Conn.SubscriptionID -Certificate $AzureCert
Select-AzureSubscription -SubscriptionId $Conn.SubscriptionID
$job = (Get-AzureAutomationJob –AutomationAccountName "THE NAME OF THE AUTOMATION ACCOUNT AS IT APPEARS IN THE PORTAL" –Name "JobStatusTest" | sort LastModifiedDate –desc)[0]
Well, you would need to use Get-AzureRMAutomation job for that. Let me elaborate on that, I think in march 2016 Microsoft removed Azure Automation from the OLD azure model and now it is present only in the new one. so you would need to add RM to your commandlets

Azure runbook to start/stop a vm says "Completed" but nothing happens

I'm quite new to Azure but I'm banging my head against something that seems simple. I've read a ton of resources that suggest this should be easy but I simply cannot get a runbook in the new portal (ARM) to start a specific VM. It runs and states "Completed" but nothing happens.
Here is the script:
Updated following Jack Zeng's comment which unfortunately changed little.
workflow StartDEVTC1
{
$VerbosePreference = "Continue"
$ErrorActionPreference = "Stop"
$WarnPreference = "Continue"
Write-Output "Getting credential..."
$cred = Get-AutomationPSCredential -Name 'AutomationPowershellCred1'
Write-Output "Adding account..."
Add-AzureRmAccount -Credential $cred
Write-Output "Getting VM..."
$VM = Get-AzureRmVM -ResourceGroupName "myResourceGroup" -Name "DEVTC1" -Status
$vmName = $VM.Name
$vmRG = $VM.ResourceGroupName
Write-Output "Checking state of $vmName from $vmRG..."
$VMDetail = $VM | Select-Object -ExpandProperty StatusesText | convertfrom-json
$vmPowerstate = $VMDetail[1].Code
if($vmPowerstate -like "PowerState/deallocated")
{
Write-Output "$vmName powerstate is $vmPowerstate, starting VM..."
$res = $VM | Start-AzureRmVM
if (($res.StatusCode) -ne 'OK')
{
Write-Error "Could not start VM."
}
}
else
{
Write-Output "$vmName powerstate is $vmPowerstate, not starting VM."
}
Write-Output "END."
}
As you can see I've tried adding some logging but I can't see these in the Testing Pane. Running the key steps in a Powershell on my PC works just fine so what's going on here?
The credential has been newly defined using the details of my azure account (I am an Owner on the account).
It should go without saying but I've tried some much simpler approaches to call Start-AzureRmVm and nothing works.
Thanks and appreciation in advance!
Ok I got it and the answer was frustratingly simple.
You cannot use a workflow block unless your runbook was created as a "PowerShell Workflow" runbook. In my case it was simply a "PowerShell" runbook which I guess was just ignoring the workflow block of code.
Recreating the runbook as a "PowerShell Workflow" runbook allowed the logging to be output so I could see what was happening and the VM got started as expected (although it took a really long time).

Get-AzureVM returning null despite having VMs under the subscription

I was working on a script which I have downloaded from the gallery to stop all VMs running in a subscription. This was running perfectly until last week, but when I ran it yesterday, it couldnt find any VM in my subscription (VMs are there in my sub and running). Script is below:
workflow Stop-AllAzureVM
{
# Add the credential used to authenticate to Azure.
# TODO: Fill in the -Name parameter with the Name of the Automation PSCredential asset
# that has access to your Azure subscription. "myPScredName" is your asset name that reflects an OrgID user
# like "someuser#somewhere.onmicrosoft.com" that has Co-Admin rights to your subscription.
$Cred = Get-AutomationPSCredential -Name "myPsCred"
# Connect to Azure
Add-AzureAccount -Credential $Cred
# Select the Azure subscription you want to work against
# TODO: Fill in the -SubscriptionName parameter with the name of your Azure subscription
Select-AzureSubscription -SubscriptionName "MySubs"
# Get all Azure VMs in the subscription that are not stopped and deallocated, and shut them down
# all at once.
$VMs = Get-AzureVM | where-object -FilterScript {$_.status -ne 'StoppedDeallocated'}
if (!$VMs)
{
Write-Output "No VM running at the moment"
}
else
{
Write-Output "VM(s) found running, proceeding for shutdown"
}
foreach -parallel ($vm in $VMs)
{
$stopRtn = Stop-AzureVM -Name $VM.Name -ServiceName $VM.ServiceName -force -ea SilentlyContinue
$count=1
if(($stopRtn.OperationStatus) -ne 'Succeeded')
{
do{
Write-Output "Failed to stop $($VM.Name). Retrying in 60 seconds..."
sleep 60
$stopRtn = Stop-AzureVM -Name $VM.Name -ServiceName $VM.ServiceName -force -ea SilentlyContinue
$count++
}
while(($stopRtn.OperationStatus) -ne 'Succeeded' -and $count -lt 5)
}
if($stopRtn){Write-Output "Stop-AzureVM cmdlet for $($VM.Name) $($stopRtn.OperationStatus) on attempt number $count of 5."}
}
}
This script always prints "No VM running at the moment"
I tried Get-AzureVM without any condition also but the result was same.
I am not sure if something has changed in past couple of weeks which is resulting in this issue.
Update-1:
I tried adding below commands in script:
$VMs = Get-AzureVM
$subscriptions = Get-AzureSubscription
Write-Output "Found [$($subscriptions.Count)] subscriptions accessible to user"
Write-Output "Found [$($VMs.Count)] VMs in Subscription"
and I got below output:
Found [] subscriptions accessible to user
Found [0] VMs in Subscription
No VM running at the moment
So it looks like something weird had happened in my automation account and I dont seem to have any clue about it!
To all who are facing this issue, not sure if this is the right way to fix this but at least as a workaround, I tried creating a new automation account and copy pasted same runbook in it and it is working fine for me.