Azure Automation Runbook Workflow looses AzContext - azure

I have written the following runbook workflow, but from time to time I see the error when it try's to start or stop a VM:
Start-AzVM : Your Azure credentials have not been set up or have expired, please run Connect-AzAccount to set up your
Azure credentials.
At StartStopVmByTag:46 char:46
+
+ CategoryInfo : CloseError: (:) [Start-AzVM], ArgumentException
+ FullyQualifiedErrorId : Microsoft.Azure.Commands.Compute.StartAzureVMCommand
I have tried passing the $azContext variable in, but I still get this issue, how can I further investigate?
workflow StartStopVmByTag {
$connectionName = "AzRunAsConnection2042";
try {
# Get the connection "AzureRunAsConnection "
$servicePrincipalConnection = Get-AutomationConnection -Name $connectionName
Write-Output "Logging in to Azure..."
$null = Add-AzAccount `
-ServicePrincipal `
-TenantId $servicePrincipalConnection.TenantId `
-ApplicationId $servicePrincipalConnection.ApplicationId `
-CertificateThumbprint $servicePrincipalConnection.CertificateThumbprint
}
catch {
if (!$servicePrincipalConnection) {
$ErrorMessage = "Connection $connectionName not found."
throw $ErrorMessage
}
else {
Write-Error -Message $_.Exception
throw $_.Exception
}
}
[DateTime]$now = [System.TimeZoneInfo]::ConvertTimeBySystemTimeZoneId((Get-Date), 'GMT Standard Time')
$startTag = 'Start Schedule'
Write-Output "*** $now - Runbook Started ***"
# Get Subscriptions
$Subscriptions = Get-AzSubscription
ForEach ($Subscription in $Subscriptions) {
$azContext = Set-AzContext -SubscriptionId $Subscription.Id
# Get all VM's with a Start or Stop Schedule
Write-Output "$($Subscription.Name): Getting VM's..."
[Array]$taggedVms = Get-AzResource -TagName $startTag -ResourceType 'Microsoft.Compute/virtualMachines'
$taggedVms = $taggedVms | Sort-Object -Property Name -Unique
# For each VM, check if start schedule is valid for now
Foreach -Parallel ($taggedVm in $taggedVms) {
Write-Output "$($Subscription.Name): Found Tagged VM: $($taggedVm.Name), $($startTag): $($taggedVm.Tags.$startTag -replace '\s', '')"
$WORKFLOW:null = Start-AzVM -ResourceGroupName $taggedVm.ResourceGroupName -Name $taggedVm.Name -DefaultProfile $azContext -NoWait
}
}
}

I have been struggling with this issue for a while, and I've tried dozens of different workarounds and nothing has worked. I finally resolved it with these registry settings that force .NET applications to use TLS 1.2. I find it very strange that this solution works, but possibly because the TLS 1.2 set as part of any parent task doesn't get passed on to the job.
They probably aren't all required, but it seems to be a best practice these days anyway.
set-itemproperty "HKLM:\SOFTWARE\Microsoft\.NETFramework\v2.0.50727" -name SystemDefaultTlsVersions -value 1 -Type DWord
set-itemproperty "HKLM:\SOFTWARE\Microsoft\.NETFramework\v4.0.30319" -name SchUseStrongCrypto -value 1 -Type DWord
set-itemproperty "HKLM:\SOFTWARE\Wow6432Node\Microsoft\.NETFramework\v2.0.50727" -name SystemDefaultTlsVersions -value 1 -Type DWord
set-itemproperty "HKLM:\SOFTWARE\Wow6432Node\Microsoft\.NETFramework\v4.0.30319" -name SchUseStrongCrypto -value 1 -Type DWord

Related

Start/Stop VMs during off-hours - Azure

Need your help to find a runbook/automation script through which I could start/stop the VM's in Azure at a specific schedule & then in case we have to delay the shutdown schedule for a particular VM, it allows us to do so. Ideally, it should notify the end user, VM is going to shutdown in 30 min or so & gives option to delay the shutdown if need be.
Is there any existing runbook available in runbook gallary within automation account? Can anyone please advise or confirm?
You can simply do this by creating power shell runbook . This is for Starting VM for working hours. you can attached this to schedule inside workbook as required.
param (
[Parameter(Mandatory=$false)]
[String] $AzureConnectionAssetName = 'AzureRunAsConnection',
[Parameter(Mandatory=$false)]
[String] $ResourceGroupName = 'Your-VM-RG'
)
#Check for Weekends
$dayOfWeek = (Get-Date).DayOfWeek
if($dayOfWeek -eq 'Saturday' -or $dayOfWeek -eq 'Sunday'){
exit
}
# Get the connection
$Conn = Get-AutomationConnection -Name $AzureConnectionAssetName -ErrorAction Stop
$null = Add-AzureRmAccount `
-ServicePrincipal `
-TenantId $Conn.TenantId `
-ApplicationId $Conn.ApplicationId `
-CertificateThumbprint $Conn.CertificateThumbprint `
-ErrorAction Stop `
-ErrorVariable err
if($err) {
throw $err
}
# Vet all VMs in the resource group
$VMs = Get-AzureRmVM -ResourceGroupName $ResourceGroupName
# Start each of the VMs
foreach ($VM in $VMs)
{
$StartRtn = $VM | Start-AzureRmVM -ErrorAction Continue
if ($StartRtn.IsSuccessStatusCode -ne $True)
{
# The VM failed to start, so send notice
Write-Output ($VM.Name + " failed to start")
Write-Error ($VM.Name + " failed to start. Error was:") -ErrorAction Continue
Write-Error (ConvertTo-Json $StartRtn) -ErrorAction Continue
}
else
{
# The VM stopped, so send notice
Write-Output ($VM.Name + " has been started")
}
}

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"

Azure Start/Stop VM via webhook

We have multiple VM's in our azure environment with multiple resourcegroups. Some of the resourcegroups have multiple VM's. We are now using an URL triggers webhook that will start or stop VM's. This is working, but when a resourcegroup contains multiple VM's all the VM's will start or all the VM's will stop instead of the one you want to start/stop.
Tried multiple scripts but it's isn't working or give me errors.
param(
[Parameter(Mandatory=$false)]
[object]
$WebHookData
)
write output "Data WebHook $WebHookData"
#retrieve ResourceGroup
$ResourceGroupName = $WebHookData.RequestBody
write output "Data ResourceGroup $ResourceGroupName"
$Conn = Get-AutomationConnection -Name AzureRunAsConnection
Connect-AzureRmAccount -ServicePrincipal -Tenant $Conn.TenantID -ApplicationId $Conn.ApplicationID -CertificateThumbprint $Conn.CertificateThumbprint
$VMs = Get-AzureRmVM -ResourceGroupName $ResourceGroupName
if(!$VMs)
{
Write-Output -InputObject 'No VMs were found in the specified Resource Group.'
}
else
{
ForEach ($VM in $VMs)
{
$StartVM = Stop-AzureRmVM -ResourceGroupName $ResourceGroupName -Name $VM.Name -Force #-ErrorAction SilentlyContinue
}
}
$message = ConvertTo-Json -Compress -InputObject ([ordered]#{
headers = #{'content-type' = 'text/plain'}
body = ''
statusCode = 200
})
You could try below script for Start/Stop Virtual machine.
Start VM
$connectionName = "AzureRunAsConnection"
$servicePrincipalConnection = Get-AutomationConnection -Name $connectionName
$null = Add-AzureRmAccount -ServicePrincipal -TenantId $servicePrincipalConnection.TenantId -ApplicationId $servicePrincipalConnection.ApplicationId -CertificateThumbprint $servicePrincipalConnection.CertificateThumbprint
$VMs = Get-AzureRmResource|Where-Object {$_.Tags.Keys -eq "owner" -and $_.Tags.Values -eq "daneum"}
foreach ($VM in $VMs) {
if ($VM.ResourceType -eq "Microsoft.Compute/virtualMachines") {
Start-AzureRmVM -ResourceGroupName $VM.ResourceGroupName -Name $VM.Name -Verbose
}
}
Stop VM
$connectionName = "AzureRunAsConnection"
$servicePrincipalConnection = Get-AutomationConnection -Name $connectionName
$null = Add-AzureRmAccount -ServicePrincipal -TenantId $servicePrincipalConnection.TenantId -ApplicationId $servicePrincipalConnection.ApplicationId -CertificateThumbprint $servicePrincipalConnection.CertificateThumbprint
$VMs = Get-AzureRmResource|Where-Object {$_.Tags.Keys -eq "owner" -and $_.Tags.Values -eq "daneum"}
foreach ($VM in $VMs) {
if ($VM.ResourceType -eq "Microsoft.Compute/virtualMachines") {
Stop-AzureRmVM -ResourceGroupName $VM.ResourceGroupName -Name $VM.Name -Force -Verbose
}
}
For webhook integration procedure you could take a look here

Azure Runbook Commands

So I have a runbook which automates the shutdown and start-up of my Azure VM during the weekends. This then sends a transactional email confirming that the VPS is shut down/started up.
I have set up my parameters as illustrated. Is there a reason as to why it correctly states the name of my virtual machine (highlighted) in the subject line but in the body of the email (highlighted), it comes up with a completely different name.
Logic would dictate that $VM.NAME would be the name of the VPS and not some random command line, so why is this? It's displayed correctly in the subject line but not the email body.
param (
[Parameter(Mandatory=$false)]
[String] $VMName ="ITAMTRADINGVPS",
[Parameter(Mandatory=$false)]
[String] $ResourceGroupName
)
$connectionName = "AzureRunAsConnection"
try
{
# Get the connection "AzureRunAsConnection "
$servicePrincipalConnection=Get-AutomationConnection -Name $connectionName
"Logging in to Azure..."
Add-AzureRmAccount `
-ServicePrincipal `
-TenantId $servicePrincipalConnection.TenantId `
-ApplicationId $servicePrincipalConnection.ApplicationId `
-CertificateThumbprint $servicePrincipalConnection.CertificateThumbprint
}
catch {
if (!$servicePrincipalConnection)
{
$ErrorMessage = "Connection $connectionName not found."
throw $ErrorMessage
} else{
Write-Error -Message $_.Exception
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 -And $VMName)
{
$VMs = Get-AzureRmVM -ResourceGroupName $ResourceGroupName -Name $VMName
}
elseif ($ResourceGroupName)
{
$VMs = Get-AzureRmVM -ResourceGroupName $ResourceGroupName
}
else
{
$VMs = Get-AzureRmVM
}
$vms
# Start each of the VMs
# Stop each of the VMs
foreach ($VM in $VMs)
{
$StopRtn = $VM | Stop-AzureRmVM -Force -ErrorAction Continue
$StopRtn
Write-Output " this is $StopRtn "
if ($StopRtn.IsSuccessStatusCode -eq 'True')
{
# The VM stopped, so send notice
Write-Output ($VM.Name + " has been stopped")
$Username ="xxx"
$Password = ConvertTo-SecureString "xxx" -AsPlainText -Force
$credential = New-Object System.Management.Automation.PSCredential $Username, $Password
$SMTPServer = "xxx"
$EmailFrom = "xxxx
[string[]]$EmailTo = "xxx"
$Subject = $VM.NAME + " notification of scheduled deallocation"
$Body = "We'd like to let you know that your Virtual Machine $VM.NAME has successfully deallocated.
<br>This could either be due to maintenance or a scheduled shutdown. If you were expecting this notification, please disregard this email.
<br><br>If you need any further assistance, please contact the system administrator on xxx<br><br>Yours Sincerely<br><br>The Technical Design Team<br>xxx<br><br>"
Send-MailMessage -smtpServer $SMTPServer -Credential $credential -Usessl -Port 587 -from $EmailFrom -to $EmailTo -subject $Subject -Body $Body -BodyAsHtml
Write-Output "Email sent succesfully."
}
else
{
# The VM failed to stop, so send notice
Write-Output ($VM.Name + " failed to stop")
}
}
Illustration
The variable reference (e.g. $VM.Name) into the email body which by default will just return the object type where the output in PowerShell is a special pipeline activity which will render the content either as a listing or as a table. In order to include content in an email, we would have to reference the properties
[string]$EmailBody = (“VMNAME IS = [{0}]” -f $VM.Name)
which is similar to string.format in C#
Refer this SO

Automation Runbook not able to set default subscription on run

Cloned a sample non classic runbook and attempted to set the subscription as default. This gives me following error:
Select-AzureSubscription : The subscription name {nameofsubscription} doesn't exist.
Parameter name: name
At step1_validate:18 char:18
+
+ CategoryInfo : CloseError: (:) [Select-AzureSubscription], ArgumentException
+ FullyQualifiedErrorId : Microsoft.WindowsAzure.Commands.Profile.SelectAzureSubscriptionCommand
How would it be possible to select or set a particular subscription as default? Below approaches throw above mentioned error:
Approach 1
Select-AzureSubscription -SubscriptionName $defaultSubscriptionname –Default
Get-AzureSubscription -SubscriptionId 123XXXXXXXXXXXXXXXXXX96eXX58 | Select-AzureSubscription
Approach 2
$subscriptionId = (Get-AzureRmSubscription | Out-GridView -Title 'Select Azure Subscription:' -PassThru).Id
Select-AzureRmSubscription -SubscriptionId $subscriptionId
Trying : To get particular resource name and make configurational changes, or to use Get-Azurewebsite cmdlet, setting a subscription appears mandatory.
Note: Hoping adding Service Principal used in the automation account to default subscription might help, I also tried to add the ApplicationId to Access control of Subscription as contributor. Though my Runbook being mentioned here and WebApps are in same subscription. Reference: https://blogs.technet.microsoft.com/knightly/2017/05/26/using-azure-automation-with-multiple-subscriptions/#comment-1555 ( Scenario for me is both are in same subscription )
EDIT: Pasting Code
<#
This PowerShell script was automatically converted to PowerShell Workflow so it can be run as a runbook.
Specific changes that have been made are marked with a comment starting with “Converter:”
#>
<#
.DESCRIPTION
To watch php version old in app, and if found turn it off and apply 5.6
.NOTES
AUTHOR: HBala
LASTEDIT: Jan 04, 2018
#>
workflow step1_validate {
# Converter: Wrapping initial script in an InlineScript activity, and passing any parameters for use within the InlineScript
# Converter: If you want this InlineScript to execute on another host rather than the Automation worker, simply add some combination of -PSComputerName, -PSCredential, -PSConnectionURI, or other workflow common parameters (http://technet.microsoft.com/en-us/library/jj129719.aspx) as parameters of the InlineScript
inlineScript {
$connectionName = "AzureRunAsConnection"
$myResourceGroupName = "DevstorageRG"
$defaultSubscriptionname = "StandardDevStaging"
$newPhpVersion = "5.6"
try
{
# Get the connection "AzureRunAsConnection "
$servicePrincipalConnection=Get-AutomationConnection -Name $connectionName
"Logging in to Azure..."
Add-AzureRmAccount `
-ServicePrincipal `
-TenantId $servicePrincipalConnection.TenantId `
-ApplicationId $servicePrincipalConnection.ApplicationId `
-CertificateThumbprint $servicePrincipalConnection.CertificateThumbprint
}
catch {
if (!$servicePrincipalConnection)
{
$ErrorMessage = "Connection $connectionName not found."
throw $ErrorMessage
} else{
Write-Error -Message $_.Exception
throw $_.Exception
}
}
#Get all ARM resources from all resource groups
$ResourceGroups = Get-AzureRmResourceGroup
foreach ($ResourceGroup in $ResourceGroups)
{
Write-Output ("Showing resources in resource group " + $ResourceGroup.ResourceGroupName)
if( $ResourceGroup.ResourceGroupName -eq $myResourceGroupName ){
$Resources = Find-AzureRmResource -ResourceGroupNameContains $ResourceGroup.ResourceGroupName | Select ResourceName, ResourceType
ForEach ($Resource in $Resources)
{
Write-Output ($Resource.ResourceName + " of type " + $Resource.ResourceType)
Write-Output ($Resource.ResourceName + " of type " + $Resource.ResourceType)
#Switch-AzureMode AzureServiceManagement
# Local powershell connects and works perfect with what I wanted to achieve.
# On Automation account, Runbook migration, it threw Azure subcription not set error for Set-AzureWebsite.
# so remove rest of the code and focused on few options to get the default as below
# which led me to post the thread.
#
# Approach 1
Select-AzureSubscription -SubscriptionName $defaultSubscriptionname –Default
Get-AzureSubscription -SubscriptionId 1238XXXXXXXXXXXe5XXXX8 | Select-AzureSubscription
# Approach 2
$subscriptionId = (Get-AzureRmSubscription | Out-GridView -Title 'Select Azure Subscription:' -PassThru).Id
Select-AzureRmSubscription -SubscriptionId $subscriptionId
Write-Output ( "==============Subscription ID :===========")
Write-Output( $subscriptionId)
# Wants to update config / php version parameters.
Set-AzureWebsite -Name $Resource.ResourceName -HttpLoggingEnabled 1 -PhpVersion 5.6
# Had tried this as well which was suggested by Jason. But appears not working..
# looks like I have messed it up.
Get-AzureRmWebApp -ResourceGroupName $myResourceGroupName -Name $Resource.ResourceName
Set-AzureRmWebApp -ResourceGroupName $myResourceGroupName -Name $Resource.ResourceName -HttpLoggingEnabled 1 -PhpVersion 5.6
}
}
Write-Output ("Completed!#Line83")
}
}
}
Hoping adding the serviceprincipal type addition for the
automationaccount to default subscription might help
You are right, we can use Azure automation account connections to select subscription.
For example, we can new a connection and the type is Azure service principal.
like this:
Then use this powershell script to select the connection:
$connectionName = "jason"
try
{
# Get the connection "jason "
$servicePrincipalConnection=Get-AutomationConnection -Name $connectionName
"Logging in to Azure..."
Add-AzureRmAccount `
-ServicePrincipal `
-TenantId $servicePrincipalConnection.TenantId `
-ApplicationId $servicePrincipalConnection.ApplicationId `
-CertificateThumbprint $servicePrincipalConnection.CertificateThumbprint
}
catch {
if (!$servicePrincipalConnection)
{
$ErrorMessage = "Connection $connectionName not found."
throw $ErrorMessage
} else{
Write-Error -Message $_.Exception
throw $_.Exception
}
}
In this way, we can use connection to select subscription.
By the way, we can follow this article to create service principal.

Resources