I'm trying to create a 'GET' method request to see if an old Azure Cloud Service (classic) is available or not - send a request and get the status code to see if its 200 OK or not.
I tried the following code:
function Get-AzCachedAccessToken() {
$AzureContext = Get-AzContext
$CurrentAzureProfile = [Microsoft.Azure.Commands.Common.Authentication.Abstractions.AzureRmProfileProvider]::Instance.Profile;
$CurrentAzureProfileClient = New-Object Microsoft.Azure.Commands.ResourceManager.Common.RMProfileClient($CurrentAzureProfile);
$AzureAccessToken = $CurrentAzureProfileClient.AcquireAccessToken($AzureContext.Tenant.Id).AccessToken;
$AzureAccessToken
}
function Get-AzBearerToken() {
$ErrorActionPreference = 'Stop'
('Bearer {0}' -f (Get-AzCachedAccessToken))
}
$CloudServiceURL = "http://test-myapp.cloudapp.net"
Invoke-RestMethod $CloudServiceURL `
-Headers #{Authorization = (Get-AzBearerToken) } `
-Method Get
The error I get is:
error msg
I'm also using an old Azure module in an automation workflow such as this: PowerShell Workflow Runbook so I could run it.
Is there anyway to restart an Azure classic cloud service role every interval?
I've written a runbook Powershell script. However I am having issues catching any errors I receive using the Connect-AzAccount command. It seems that wrapping the Connect-AzAccount in a try catch does not work. The script continues. How can I handle the errors returned using this command?
try
{
The catch is never executed even when there is an error
Connect-AzAccount -Identity;
}
catch
{
write-output "Error connecting to Azure";
write-output $_.Exception.message;
$response = .\SendEmail.ps1 -To "xxx#xxx.com" -From "xxx#xxx" -Subject "xxxx" -Message "xxxxx";
}
The error message below is rendered to the console automatically
Unable to acquire token for tenant 'organizations'
Connect-AzAccount : ManagedIdentityCredential authentication unavailable. The requested identity has not been assigned
to this resource.
Status: 400 (Bad Request)
Content:
{"error":"invalid_request","error_description":"Identity not found"}
Headers:
Content-Length: xx
Content-Type: xxxxx
Date: xxxxx
Server: xxxx/xxxxxxxxxx
At line:71 char:5
+ Connect-AzAccount -Identity;
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : CloseError: (:) [Connect-AzAccount], CredentialUnavailableException
+ FullyQualifiedErrorId : Microsoft.Azure.Commands.Profile.ConnectAzureRmAccountCommand
As far as I know in order for the catch to be executed, you should include -ErrorAction Stop.
So your code should read:
Connect-AzAccount -Identity -ErrorAction Stop
You could also set:
$ErrorActionPreference = "Stop" at the top of your cmdlet.
I've attempted to set up a simple webhook execution in Azure Data Factory (v2), calling a simple (parameter-less) webhook for an Azure Automation Runbook I set up.
From the Azure Portal, I can see that the webhook is being executed and my runbook is being run, so far so good. The runbook is (currently) returning an error within 1 minute of execution - but that's fine, I also want to test failure scenarios.
Problem:
Data Factory doesn't seem to be 'seeing' the error result and spins until the timeout (10 minutes) elapses. When I kick off a debug run of the pipeline, I get the same - a timeout and no error result.
Update: I've fixed the runbook and it's now completing successfully, but Data Factory is still timing out and is not seeing the success response either.
Here is a screenshot of the setup:
And here is the portal confirming that the webhook is being run by azure data factory, and is completing in under a minute:
WEBHOOKDATA JSON is:
{"WebhookName":"Start CAMS VM","RequestBody":"{\r\n \"callBackUri\": \"https://dpeastus.svc.datafactory.azure.com/dataplane/workflow/callback/f7c...df2?callbackUrl=AAEAFF...0927&shouldReportToMonitoring=True&activityType=WebHook\"\r\n}","RequestHeader":{"Connection":"Keep-Alive","Expect":"100-continue","Host":"eab...ddc.webhook.eus2.azure-automation.net","x-ms-request-id":"7b4...2eb"}}
So as far as I can tell, things should be in place to pick up on the result (success of failure). Hopefully someone who's done this before knows what I'm missing.
Thanks!
I had assumed that Azure would automatically notify the ADF "callBackUri" with the result once the runbook completed or errored out (since they take care of 99% of the scaffolding without requiring a line of code).
It turns out that is not the case, and anyone wishing to execute a runbook from ADF will have to manually extract the callBackUri from the Webhookdata input parameter, and POST the result to it when done.
I haven't nailed this down yet, since the Microsoft tutorial sites I've found have a bad habit of taking screenshots of the code that does this rather than providing the code itself:
I guess I'll come back and edit this once I have it figured out.
EDIT I ended up implementing this by leaving my original Webhook untouched, and creating a "wrapper"/helper/utility Runbook that will execute an arbitrary webhook, and relay its status to ADF once it's complete.
Here's the full code I ended up with, in case it helps someone else. It's meant to be generic:
Setup / Helper Functions
param
(
[Parameter (Mandatory = $false)]
[object] $WebhookData
)
Import-Module -Name AzureRM.resources
Import-Module -Name AzureRM.automation
# Helper function for getting the current running Automation Account Job
# Inspired heavily by: https://github.com/azureautomation/runbooks/blob/master/Utility/ARM/Find-WhoAmI
<#
Queries the automation accounts in the subscription to find the automation account, runbook and resource group that the job is running in.
AUTHOR: Azure/OMS Automation Team
#>
Function Find-WhoAmI {
[CmdletBinding()]
Param()
Begin { Write-Verbose ("Entering {0}." -f $MyInvocation.MyCommand) }
Process {
# Authenticate
$ServicePrincipalConnection = Get-AutomationConnection -Name "AzureRunAsConnection"
Add-AzureRmAccount `
-ServicePrincipal `
-TenantId $ServicePrincipalConnection.TenantId `
-ApplicationId $ServicePrincipalConnection.ApplicationId `
-CertificateThumbprint $ServicePrincipalConnection.CertificateThumbprint | Write-Verbose
Select-AzureRmSubscription -SubscriptionId $ServicePrincipalConnection.SubscriptionID | Write-Verbose
# Search all accessible automation accounts for the current job
$AutomationResource = Get-AzureRmResource -ResourceType Microsoft.Automation/AutomationAccounts
$SelfId = $PSPrivateMetadata.JobId.Guid
foreach ($Automation in $AutomationResource) {
$Job = Get-AzureRmAutomationJob -ResourceGroupName $Automation.ResourceGroupName -AutomationAccountName $Automation.Name -Id $SelfId -ErrorAction SilentlyContinue
if (!([string]::IsNullOrEmpty($Job))) {
return $Job
}
Write-Error "Could not find the current running job with id $SelfId"
}
}
End { Write-Verbose ("Exiting {0}." -f $MyInvocation.MyCommand) }
}
Function Get-TimeStamp {
return "[{0:yyyy-MM-dd} {0:HH:mm:ss}]" -f (Get-Date)
}
My Code
### EXPECTED USAGE ###
# 1. Set up a webhook invocation in Azure data factory with a link to this Runbook's webhook
# 2. In ADF - ensure the body contains { "WrappedWebhook": "<your url here>" }
# This should be the URL for another webhook.
# LIMITATIONS:
# - Currently, relaying parameters and authentication credentials is not supported,
# so the wrapped webhook should require no additional authentication or parameters.
# - Currently, the callback to Azure data factory does not support authentication,
# so ensure ADF is configured to require no authentication for its callback URL (the default behaviour)
# If ADF executed this runbook via Webhook, it should have provided a WebhookData with a request body.
if (-Not $WebhookData) {
Write-Error "Runbook was not invoked with WebhookData. Args were: $args"
exit 0
}
if (-Not $WebhookData.RequestBody) {
Write-Error "WebhookData did not contain a ""RequestBody"" property. Data was: $WebhookData"
exit 0
}
$parameters = (ConvertFrom-Json -InputObject $WebhookData.RequestBody)
# And this data should contain a JSON body containing a 'callBackUri' property.
if (-Not $parameters.callBackUri) {
Write-Error 'WebhookData was missing the expected "callBackUri" property (which Azure Data Factory should provide automatically)'
exit 0
}
$callbackuri = $parameters.callBackUri
# Check for the "WRAPPEDWEBHOOK" parameter (which should be set up by the user in ADF)
$WrappedWebhook = $parameters.WRAPPEDWEBHOOK
if (-Not $WrappedWebhook) {
$ErrorMessage = 'WebhookData was missing the expected "WRAPPEDWEBHOOK" peoperty (which the user should have added to the body via ADF)'
Write-Error $ErrorMessage
}
else
{
# Now invoke the actual runbook desired
Write-Output "$(Get-TimeStamp) Invoking Webhook Request at: $WrappedWebhook"
try {
$OutputMessage = Invoke-WebRequest -Uri $WrappedWebhook -UseBasicParsing -Method POST
} catch {
$ErrorMessage = ("An error occurred while executing the wrapped webhook $WrappedWebhook - " + $_.Exception.Message)
Write-Error -Exception $_.Exception
}
# Output should be something like: {"JobIds":["<JobId>"]}
Write-Output "$(Get-TimeStamp) Response: $OutputMessage"
$JobList = (ConvertFrom-Json -InputObject $OutputMessage).JobIds
$JobId = $JobList[0]
$OutputMessage = "JobId: $JobId"
# Get details about the currently running job, and assume the webhook job is being run in the same resourcegroup/account
$Self = Find-WhoAmI
Write-Output "Current Job '$($Self.JobId)' is running in Group '$($Self.ResourceGroupName)' and Automation Account '$($Self.AutomationAccountName)'"
Write-Output "Checking for Job '$($JobId)' in same Group and Automation Account..."
# Monitor the job status, wait for completion.
# Check against a list of statuses that likely indicate an in-progress job
$InProgressStatuses = ('New', 'Queued', 'Activating', 'Starting', 'Running', 'Stopping')
# (from https://learn.microsoft.com/en-us/powershell/module/az.automation/get-azautomationjob?view=azps-4.1.0&viewFallbackFrom=azps-3.7.0)
do {
# 1 second between polling attempts so we don't get throttled
Start-Sleep -Seconds 1
try {
$Job = Get-AzureRmAutomationJob -Id $JobId -ResourceGroupName $Self.ResourceGroupName -AutomationAccountName $Self.AutomationAccountName
} catch {
$ErrorMessage = ("An error occurred polling the job $JobId for completion - " + $_.Exception.Message)
Write-Error -Exception $_.Exception
}
Write-Output "$(Get-TimeStamp) Polled job $JobId - current status: $($Job.Status)"
} while ($InProgressStatuses.Contains($Job.Status))
# Get the job outputs to relay to Azure Data Factory
$Outputs = Get-AzureRmAutomationJobOutput -Id $JobId -Stream "Any" -ResourceGroupName $Self.ResourceGroupName -AutomationAccountName $Self.AutomationAccountName
Write-Output "$(Get-TimeStamp) Outputs from job: $($Outputs | ConvertTo-Json -Compress)"
$OutputMessage = $Outputs.Summary
Write-Output "Summary ouput message: $($OutputMessage)"
}
# Now for the entire purpose of this runbook - relay the response to the callback uri.
# Prepare the success or error response as per specifications at https://learn.microsoft.com/en-us/azure/data-factory/control-flow-webhook-activity#additional-notes
if ($ErrorMessage) {
$OutputJson = #"
{
"output": { "message": "$ErrorMessage" },
"statusCode": 500,
"error": {
"ErrorCode": "Error",
"Message": "$ErrorMessage"
}
}
"#
} else {
$OutputJson = #"
{
"output": { "message": "$OutputMessage" },
"statusCode": 200
}
"#
}
Write-Output "Prepared ADF callback body: $OutputJson"
# Post the response to the callback URL provided
$callbackResponse = Invoke-WebRequest -Uri $callbackuri -UseBasicParsing -Method POST -ContentType "application/json" -Body $OutputJson
Write-Output "Response was relayed to $callbackuri"
Write-Output ("ADF replied with the response: " + ($callbackResponse | ConvertTo-Json -Compress))
At a high-level, steps I've taken are to:
Execute the "main" Webhook - get back a "Job Id"
Get the current running job's "context" (resource group and automation account info) so that I can poll the remote job.
Poll the job until it is complete
Put together either a "success" or "error" response message in the format that Azure Data Factory expects.
Invoke the ADF callback.
For those looking, I created a secondary approach to the above solution - one that executes a Runbook (with parameters) from a Webhook, rather than a invoking a nested Webhook. This has a couple of benefits:
Parameters can be passed through to the Runbook (rather than requiring parameters to be baked-into a new Webhook.
A Runbook from another Azure Automation Account / Resource Group can be invoked.
There's no need to poll the status of the job, since the Start-AzureRmAutomationRunbook commandlet has a -Wait parameter.
Here's the code:
param
(
# Note: While "WebhookData" is the only root-level parameter (set by Azure Data Factory when it invokes this webhook)
# The user should ensure they provide (via the ADF request body) these additional properties required to invoke the runbook:
# - RunbookName
# - ResourceGroupName (TODO: Can fill this in by default if not provided)
# - AutomationAccountName (TODO: Can fill this in by default if not provided)
# - Parameters (A nested dict containing parameters to forward along)
[Parameter (Mandatory = $false)]
[object] $WebhookData
)
Import-Module -Name AzureRM.resources
Import-Module -Name AzureRM.automation
Function Get-TimeStamp {
return "[{0:yyyy-MM-dd} {0:HH:mm:ss}]" -f (Get-Date)
}
# If ADF executed this runbook via Webhook, it should have provided a WebhookData with a request body.
if (-Not $WebhookData) {
Write-Error "Runbook was not invoked with WebhookData. Args were: $args"
exit 0
}
if (-Not $WebhookData.RequestBody) {
Write-Error "WebhookData did not contain a ""RequestBody"" property. Data was: $WebhookData"
exit 0
}
$parameters = (ConvertFrom-Json -InputObject $WebhookData.RequestBody)
# And this data should contain a JSON body containing a 'callBackUri' property.
if (-Not $parameters.callBackUri) {
Write-Error 'WebhookData was missing the expected "callBackUri" property (which Azure Data Factory should provide automatically)'
exit 0
}
$callbackuri = $parameters.callBackUri
# Check for required parameters, and output any errors.
$ErrorMessage = ''
$RunbookName = $parameters.RunbookName
$ResourceGroupName = $parameters.ResourceGroupName
$AutomationAccountName = $parameters.AutomationAccountName
if (-Not $RunbookName) {
$ErrorMessage += 'WebhookData was missing the expected "RunbookName" property (which the user should have added to the body via ADF)`n'
} if (-Not $ResourceGroupName) {
$ErrorMessage += 'WebhookData was missing the expected "ResourceGroupName" property (which the user should have added to the body via ADF)`n'
} if (-Not $AutomationAccountName) {
$ErrorMessage += 'WebhookData was missing the expected "AutomationAccountName" property (which the user should have added to the body via ADF)`n'
} if ($ErrorMessage) {
Write-Error $ErrorMessage
} else {
# Set the current automation connection's authenticated account to use for future Azure Resource Manager cmdlet requests.
# TODO: Provide the user with a way to override this if the target runbook doesn't support the AzureRunAsConnection
$ServicePrincipalConnection = Get-AutomationConnection -Name "AzureRunAsConnection"
Add-AzureRmAccount -ServicePrincipal `
-TenantId $ServicePrincipalConnection.TenantId `
-ApplicationId $ServicePrincipalConnection.ApplicationId `
-CertificateThumbprint $ServicePrincipalConnection.CertificateThumbprint | Write-Verbose
Select-AzureRmSubscription -SubscriptionId $ServicePrincipalConnection.SubscriptionID | Write-Verbose
# Prepare the properties to pass on to the next runbook - all provided properties exept the ones specific to the ADF passthrough invocation
$RunbookParams = #{ }
if($parameters.parameters) {
$parameters.parameters.PSObject.Properties | Foreach { $RunbookParams[$_.Name] = $_.Value }
Write-Output "The following parameters will be forwarded to the runbook: $($RunbookParams | ConvertTo-Json -Compress)"
}
# Now invoke the actual runbook desired, and wait for it to complete
Write-Output "$(Get-TimeStamp) Invoking Runbook '$($RunbookName)' from Group '$($ResourceGroupName)' and Automation Account '$($AutomationAccountName)'"
try {
# Runbooks have this nice flag that let you wait on their completion (unlike webhook-invoked)
$Result = Start-AzureRmAutomationRunbook -Wait -Name $RunbookName -AutomationAccountName $AutomationAccountName -ResourceGroupName $ResourceGroupName –Parameters $RunbookParams
} catch {
$ErrorMessage = ("An error occurred while invoking Start-AzAutomationRunbook - " + $_.Exception.Message)
Write-Error -Exception $_.Exception
}
# Digest the result to be relayed to ADF
if($Result) {
Write-Output "$(Get-TimeStamp) Response: $($Result | ConvertTo-Json -Compress)"
$OutputMessage = $Result.ToString()
} elseif(-Not $ErrorMessage) {
$OutputMessage = "The runbook completed without errors, but the result was null."
}
}
# Now for the entire purpose of this runbook - relay the response to the callback uri.
# Prepare the success or error response as per specifications at https://learn.microsoft.com/en-us/azure/data-factory/control-flow-webhook-activity#additional-notes
if ($ErrorMessage) {
$OutputJson = #{
output = #{ message = $ErrorMessage }
statusCode = 500
error = #{
ErrorCode = "Error"
Message = $ErrorMessage
}
} | ConvertTo-Json -depth 2
} else {
$OutputJson = #{
output = #{ message = $OutputMessage }
statusCode = 200
} | ConvertTo-Json -depth 2
}
Write-Output "Prepared ADF callback body: $OutputJson"
# Post the response to the callback URL provided
$callbackResponse = Invoke-WebRequest -Uri $callbackuri -UseBasicParsing -Method POST -ContentType "application/json" -Body $OutputJson
Write-Output "Response was relayed to $callbackuri"
Write-Output ("ADF replied with the response: " + ($callbackResponse | ConvertTo-Json -Compress))
I've been trying to create Azure Container instance using http triggered powershell Azure Function.
I've wrote some code and it works... kind of
using namespace System.Net
param($Request, $TriggerMetadata)
$appId = "appID"
$appPassword = "appPassword"
$secpasswd = ConvertTo-SecureString $appPassword -AsPlainText -Force
$mycred = New-Object System.Management.Automation.PSCredential ($appId, $secpasswd)
$status = [HttpStatusCode]::OK
New-AzContainerGroup -ResourceGroupName "test" -Name "testName" `
-Image "imageName" `
-RegistryCredential $mycred `
-RestartPolicy Never
Push-OutputBinding -Name Response -Value ([HttpResponseContext]#{
StatusCode = $status
Body = $body
})
When i hit endpoint, i always get 500 response code, but for some reason container instance is being created anyway and it works. Here's error from terminal:
Executed 'Functions.testFuntion' (Failed, Id=240084c5-6aa2-4207-ae84-66f85a03504b)
System.Private.CoreLib: Exception while executing function: Functions.testFuntion.
System.Private.CoreLib: Result: Failure
Exception: Object reference not set to an instance of an object.
Stack:
at
Microsoft.Azure.Functions.PowerShellWorker.Utility.TypeExtensions.
DeriveContentType(HttpResponseContext
Microsoft.Azure.Functions.PowerShellWorker.Utility.TypeExtensions.DeriveContentType
(HttpResponseContext httpResponseContext, RpcHttp rpcHttp) in C:\projects\azure-functions-powershell-
worker\src\Utility\TypeExtensions.cs:line 196
at
Microsoft.Azure.Functions.PowerShellWorker.Utility.TypeExtensions.ToRpcHttp(HttpResponseContext
httpResponseContext) in C:\projects\azure-functions-powershell-
worker\src\Utility\TypeExtensions.cs:line 188
at
Microsoft.Azure.Functions.PowerShellWorker.Utility.TypeExtensions.ToTypedData(Object value) in
C:\projects\azure-functions-powershell-worker\src\Utility\TypeExtensions.cs:line 242
at
Microsoft.Azure.Functions.PowerShellWorker.RequestProcessor.BindOutputFromResult(InvocationResponse
response, AzFunctionInfo functionInfo, Hashtable results) in C:\projects\azure-functions-powershell-
worker\src\RequestProcessor.cs:line 465
at
Microsoft.Azure.Functions.PowerShellWorker.RequestProcessor.ProcessInvocationRequestImpl
(StreamingMessage request, AzFunctionInfo functionInfo, PowerShellManager psManager,
FunctionInvocationPerformanceStopwatch stopwatch)
in C:\projects\azure-functions-powershell-worker\src\RequestProcessor.cs:line 305.
Is this something i should be worried about? Even though i get 500 response, everything seems to be working.
You may need to define $body before using it in the Push-OutputBinding response.
Something like:
$body = "My Message"
Push-OutputBinding -Name Response -Value ([HttpResponseContext]#{
StatusCode = $status
Body = $body
})
I am using below command in Azure Automation Powershell script to get status of Recovery Services container - it simply checks if a VM is registered for backup.
Get-AzureRmRecoveryServicesVault -Name $AzureRecoveryServicesName | Set-AzureRmRecoveryServicesVaultContext
$BackupStatus = Get-AzureRmRecoveryServicesBackupContainer -ContainerType AzureVM -FriendlyName $VirtualMachineName -ResourceGroupName $ResourceGroupName
If a VM is not registered it throws an error and $BackupStatus is empty:
Get-AzureRmRecoveryServicesBackupContainer : Requested value 'NotRegistered' was not found.
At line:53 char:21
... kupStatus = Get-AzureRmRecoveryServicesBackupContainer -ContainerType ...
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
CategoryInfo : InvalidArgument: (:) [Get-AzureRmReco...BackupContainer], ArgumentException
FullyQualifiedErrorId :
Microsoft.Azure.Commands.RecoveryServices.Backup.Cmdlets.GetAzureRmRecoveryServicesBackupContainer
And if it is already registered I get $BackupStatus = Microsoft.Azure.Commands.RecoveryServices.Backup.Cmdlets.Models.AzureVmContainer.
I've tried:
$BackupStatus = (Get-AzureRmRecoveryServicesBackupContainer -ContainerType AzureVM -FriendlyName $VirtualMachineName -ResourceGroupName $ResourceGroupName).Status
But then the only difference is that I get $BackupStatus = Registered when VM is already in a container but same error occurs when VM is not registered.
What is the proper way to check if a VM is registered for backup or not? I am ok with what I'm getting - I can handle $BackupStatus values in a script - but I don't want this error message to show.
Strange thing is that in Powershell on PC with same Azure modules versions loaded (AzureRM​.RecoveryServices​.Backup 2.5.0) as in Automation Account I am getting a different behavior:
PS C:\windows\system32> $BackupStatus = Get-AzureRmRecoveryServicesBackupContainer -ContainerType AzureVM -FriendlyName $VirtualMachineName -ResourceGroupName $ResourceGroupName
PS C:\windows\system32> $BackupStatus
Name ResourceGroupName Status ContainerType
---- ----------------- ------ -------------
UbuntuBckptest rhel68128 Registered AzureVM
It returns an object instead of a string.
If you want to set $BackupStatus value according to Get-AzureRmRecoveryServicesBackupContainer command executed result. We could put the Get-AzureRmRecoveryServicesBackupContainer in try...catch.
The following just the code sample
$BackupStatus;
try
{
$BackupStatus=Get-AzureRmRecoveryServicesBackupContainer
}
catch
{
# we can add condition for example : if($BackupStatus.Contains("NotRegistered"))
$BackupStatus = "NotRegistered"
}
# we can add condition :get the type of $BackupStatus for example :if($BackupStatus.GetType().Name.equals("String"))`
$BackupStatus = "Registered"