Using REST API from PowerShell to get additional Azure VM Status Details

There are many ways to get information about your IaaS / PaaS components from Azure.  The obvious and easy way is to use the Portal (or the new Preview Portal).  We, of course, are users of the Azure PowerShell SDK or even the command-line interface.  The PowerShell cmdlets for Azure are great, but they don’t always return everything I want to know or even do everything that is possible in Azure.  This is why knowing multiple paths is very important.  In this specific case, we are going to look at the Service Management REST API.

Where Do I Start?

The first thing to do is use the Get-AzurePublishSettings file to download your subscriptions certificate.  Without it, accessing the REST API is not quite so simple or straightforward.  You can use the previously mentioned cmdlet or go to the download page at https://manage.windowsazure.com/publishsettings/.  Once you’ve downloaded the .publishsettings file, you’ll use Import-AzurePublishSettingsFile along with the PublishSettingsFile parameter pointing to the file you just downloaded.  Now you have the appropriate certificates in place to easily work with the REST API.

Finding Your URI

The next three pieces of information you need are the Subscription ID, the Cloud Service Deployment, and the Header date (x-ms-version).  The fastest way to get this information is to run a VM cmdlet (I use Get-AzureVM) with the –Debug parameter.  This will force the cmdlet to spit out your URI, the Headers, as well as the output that the cmdlet is ultimately parsing for you.  Do so now and take a look at the output you get.  The URI / Header are in the HTTP Request segment and the HTTP Response has a Body property as an XML formatted response with details about your Deployment.

Note the Headers on the HTTP Response, you can see which region of Azure responded to your request, as well as some date, version, and request specific details.  You’ll also see a secondary HTTP Request / HTTP Response in the debug information.  This is a check and answer on the operation itself to validate it completed successful or return an error otherwise.  Then the standard output of the cmdlet is shown.

What you’ll have discovered at this point, is that the URI for your Cloud Service Deployment follows this pattern:

 https://management.core.windows.net/{SubscriptionId}/services/hostedservices/{vmServiceName}/deploymentslots/Production

Also, you should have spotted the ‘x-ms-version’ header which had the following value: 2014-06-01

Creating our REST-based cmdlet

Now that we know what we are calling and what to expect back, let’s wrap this into a simple cmdlet to help us get this information (we’ll handle parsing a bit further down).

<# .Synopsis Retrieves the status of a VM and installed extensions #>
function Get-AzureVMStatus
{
    [CmdletBinding()]
    param (
        [Parameter(ParameterSetName="ByVm", Position=1, Mandatory, ValueFromPipeline)]
            [Microsoft.WindowsAzure.Commands.ServiceManagement.Model.PersistentVMRoleContext] $VM,
        [Parameter(ParameterSetName="ByName", Position=1, Mandatory)]
            [string] $Name,
        [Parameter(ParameterSetName="ByName", Position=2, Mandatory)]
            [string] $ServiceName,
        [Parameter()] 
            [string] $SubscriptionId
    )

    if ($PSCmdlet.ParameterSetName -eq "ByVm")
    {
        $vmName = $VM.Name
        $vmServiceName = $VM.ServiceName
    }
    else
    {
        $vmName = $Name
        $vmServiceName = $ServiceName
    }

    if (!$SubscriptionId)
    {
        $SubscriptionId = (Get-AzureSubscription | ? IsDefault).SubscriptionId
    }

    $account = Get-AzureAccount | ? { $_.Subscriptions.Contains($SubscriptionId) -and $_.Type -eq "Certificate" } 

    if (!$account)
    {
        throw "Can't find an account for Azure subscription $SubscriptionId"
    }
    
    $certificate = ls Cert:\CurrentUser\My | ? Thumbprint -eq $account.Id

    if (!$certificate)
    {
        throw "Can't find the certificate for Azure account {0}" -f $account.Id
    }

    $uri = "https://management.core.windows.net/${SubscriptionId}/services/hostedservices/${vmServiceName}/deploymentslots/Production"
    
    try
    {
        $response = Invoke-WebRequest -Method GET -Uri $uri -Certificate $certificate -Headers @{ "x-ms-version" = "2014-06-01" } -ErrorAction Ignore
    }
    catch
    {
        $message = ([xml] $_.ErrorDetails.Message)
        throw "{0}: {1}" -f $message.Error.Code, $message.Error.Message
    }

    $content = [xml] $response.Content    
    $content.ChildNodes
}

As you can see, we are using Invoke-WebRequest to get the URL we strung together while making use of our Certificate for authentication and the appropriate Header.  We then convert the response to XML and end with the ChildNodes of the response.

Give it a try and see that you get a parsed table of information back that are all the nodes that were immediately under the Deployment hive in the original REST response.  You can either pass the cmdlet the ServiceName and VM Name or you can pipeline pass it a VM object (from Get-AzureVM).

Using the new cmdlet to get data

A quick example of some data we can get would be looking at the endpoints.

 PS C:\> (Get-AzureVMStatus -Name 'DC0' -ServiceName 'DCtheGeek-DC').RoleInstanceList.RoleInstance.InstanceEndpoints

InstanceEndpoint                                                                                                                                       
----------------                                                                                                                                       
{PowerShell, RemoteDesktop} 

Using the response from the cmdlet like XML (since it is), I can parse down the child nodes and find the values I want.  We got the list of Endpoints back, but I want details about the RemoteDesktop.  Here’s what that would look like:

 PS C:\> (Get-AzureVMStatus -Name 'DC0' -ServiceName 'DCtheGeek-DC').RoleInstanceList.RoleInstance.
>>>  InstanceEndpoints.InstanceEndpoint | Where-Object { $_.Name -eq 'RemoteDesktop' }


Name       : RemoteDesktop
Vip        : 104.45.230.250
PublicPort : 59456
LocalPort  : 3389
Protocol   : tcp 

If I was looking to just grab the Public Port of this Endpoint, I could finish the example like so:

 PS C:\> ((Get-AzureVMStatus -Name 'DC0' -ServiceName 'DCtheGeek-DC').RoleInstanceList.RoleInstance.
>>>  InstanceEndpoints.InstanceEndpoint | Where-Object { $_.Name -eq 'RemoteDesktop' }).PublicPort
59456 

How about using this to get DSC Extension status?

Of course!  That’s actually where this project began, so let’s take a look at that.  If we go back and look at the full REST output, the Extension data is under the nodes:

 PS C:\> (Get-AzureVMStatus -Name 'DC0' -ServiceName 'DCtheGeek-DC').RoleInstanceList.RoleInstance.
>>> ResourceExtensionStatusList.ResourceExtensionStatus

HandlerName                           Version                               Status                                FormattedMessage                     
-----------                           -------                               ------                                ----------------                     
Microsoft.Compute.BGInfo              1.1                                   Ready                                 FormattedMessage                     
Microsoft.Compute.CustomScriptExte... 1.1                                   Ready                                                                      
Microsoft.Powershell.DSC              1.3.0.0                               Ready                                                                      

If we look at this, it looks like the DSC extension is in a good status, ‘Ready’.  Don’t be fooled by this.  This simply means that the agent is indeed ready for more instructions.  It doesn’t tell me the details of the last execution.  For that we have to go farther into the extension status, like so:

 PS C:\> ((Get-AzureVMStatus -Name 'DC0' -ServiceName 'DCtheGeek-DC').RoleInstanceList.RoleInstance.
>>>  ResourceExtensionStatusList.ResourceExtensionStatus | 
>>>  Where-Object { $_.HandlerName -eq 'Microsoft.Powershell.DSC' }).ExtensionSettingStatus

Timestamp                                          Status                                             FormattedMessage                                 
---------                                          ------                                             ----------------                                 
2014-10-17T01:16:13Z                               Error                                              FormattedMessage                                 

Hey!  Now we can see that the extension has an error, and we should be able to grab the text:

 PS C:\> ((Get-AzureVMStatus -Name 'DC0' -ServiceName 'DCtheGeek-DC').RoleInstanceList.RoleInstance.
>>>  ResourceExtensionStatusList.ResourceExtensionStatus | 
>>>  Where-Object { $_.HandlerName -eq 'Microsoft.Powershell.DSC' }).ExtensionSettingStatus.FormattedMessage.Message
An error occurred downloading the Azure Blob: The remote server returned an error: (403) Forbidden.
The Set-AzureVMDscExtension cmdlet grants access to the blobs only for 1 hour; have you exceeded that interval?

Review

We’ve taken a look at using the Service Management REST API and getting some info out of it using a custom cmdlet.  There is obviously a lot of work that could be done to create additional cmdlets for better parsing and filtering of the base output, but we’ll save that for another time!

Special Thanks

Thanks again to Norberto Arrieta!!  He provided the starter code for working with the Service Manager REST API and got me going down the right path.