PowerShell and the Nimble Storage REST API

If you read my previous blog article on PowerShell Function to Determine the Installed VSS Providers then you’re already aware that I recently migrated one of my customers to a Nimble Storage Area Network.

While Nimble does have a PowerShell module and it’s decent, I wanted to see how difficult it is to work with their REST API directly with PowerShell. Their REST API documentation also seems to be decent.

Note: All of the functions shown in this blog article are a proof of concept and should be thoroughly tested before attempting to use them in a production environment.

First of all, you need to connect to the Nimble SAN group so I wrote a function for that:

#Requires -Version 4.0 -Module TunableSSLValidator
function Connect-MrNSGroup {

<#
.SYNOPSIS
    Connects to a Nimble SAN.

.DESCRIPTION
    Connect-MrNSGroup is an advanced function that provides the initial connection to a Nimble SAN
    so that other subsequent commands can be run without having to each authenticate individually.
    The TunableSSLValidator module is required since Nimble uses an untrusted SSL certificate. It
    can be found on GitHub: https://github.com/Jaykul/Tunable-SSL-Validator

.PARAMETER Group
    The DNS name or IP address of the Nimble group.

.PARAMETER Port
    The port number for the Nimble REST API. The default value is 5392. This parameter is hidden and
    provided only as a means to easily change the port number if the API ever changes.

.PARAMETER Credential
    Specifies a user account that has permission to perform this action. Type a user name, such as User01
     or enter a PSCredential object, such as one generated by the Get-Credential cmdlet. If you type a
     user name, this function prompts you for a password.

.EXAMPLE
     Connect-MrNSGroup -Group nimblegroup.yourdns.local -Credential (Get-Credential)

.EXAMPLE
     Connect-MrNSGroup -Group 192.168.1.50 -Credential (Get-Credential)

.INPUTS
    None

.OUTPUTS
    None

.NOTES
    Author:  Mike F Robbins
    Website: http://mikefrobbins.com
    Twitter: @mikefrobbins
#>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory)]
        [string]$Group,

        [Parameter(DontShow)]
        [ValidateNotNullOrEmpty()]
        [int]$Port = 5392,

        [Parameter(Mandatory)]
        [System.Management.Automation.Credential()]$Credential
    )

    $Uri = "https://$($Group):$($Port)"

    $Script:tokenData = TunableSSLValidator\Invoke-RestMethod -Uri "$Uri/v1/tokens" -InSecure -Method Post -Body ((@{data = @{username = $Credential.UserName;password = $Credential.GetNetworkCredential().password}}) | ConvertTo-Json)
    $Script:RestVersion = (TunableSSLValidator\Invoke-RestMethod -Uri "$Uri/versions" -Insecure).data.name
    $Script:session_token = $tokenData.data.session_token
    $Script:array = $Group
}

I’m not a big fan of globally scoping variables unless absolutely necessary, but in this scenario unless you want to authenticate with every command, it does seem to be necessary. Their SAN uses a self-signed SSL certificate and I found that fellow Microsoft MVP Joel Bennett had written a PowerShell module named TunableSSLValidator that adds an Insecure parameter to Invoke-RestMethod which kept me from having to worry about writing the code to handle it myself.

Update: Based on feedback from Joel Bennett, I changed the variable scoping from Global to Script to limit their access to only the module.

While many examples you might find store things like the value for Body parameter in a variable, the password is decrypted and then transmitted using the SSL encryption so I didn’t want the plain text password hanging around in a variable for however long I leave my current PowerShell session open so I decided to perform the decryption inline when that line of code is executed.

This next function gets the session tokens for users who are connected to the Nimble SAN group:

#Requires -Version 4.0 -Module TunableSSLValidator
function Get-MrNSToken {

<#
.SYNOPSIS
    List user session tokens.

.DESCRIPTION
    Get-MrNSToken is an advanced function that lists user session tokens for the specified Nimble SAN.
    The TunableSSLValidator module is required since Nimble uses an untrusted SSL certificate. It can
    be found on GitHub: https://github.com/Jaykul/Tunable-SSL-Validator

.PARAMETER Group
    The DNS name or IP address of the Nimble group. The default is the group that you've already connected
    to using the Connect-MrNSGroup function.

.PARAMETER Port
    The port number for the Nimble REST API. The default value is 5392. This parameter is hidden and
    provided only as a means to easily change the port number if the API ever changes.

.EXAMPLE
     Get-NStoken

.INPUTS
    None

.OUTPUTS
    MrNS.Token

.NOTES
    Author:  Mike F Robbins
    Website: http://mikefrobbins.com
    Twitter: @mikefrobbins
#>

    [CmdletBinding()]
    param (
        [Parameter(DontShow)]
        [ValidateNotNullOrEmpty()]
        [string]$Group = $array,

        [Parameter(DontShow)]
        [ValidateNotNullOrEmpty()]
        [int]$Port = 5392
    )

    $Uri = "https://$($Group):$($Port)"

    $Header = @{'X-Auth-Token' = $session_token}

    $Tokens = TunableSSLValidator\Invoke-RestMethod -Uri "$Uri/v1/tokens" -Method Get -Header $Header -Insecure

    foreach ($Token in $Tokens.data.id){

        $TokenUri = "$Uri/v1/tokens/$Token"

        $Result = (TunableSSLValidator\Invoke-RestMethod -Uri $TokenUri -Method Get -Header $header -Insecure).data
        $Result.PSTypeNames.Insert(0,'MrNS.Token')

        Write-Output $Result
    }

}

I decided to go ahead and turn these functions into a module so it would be easier to add both custom formatting and custom types. One thing I’ve found is most SAN vendors simply give you everything back from the API. That ends up being an overwhelming amount of information so I decided to only return a few key properties in a table by default without having to hard code anything or pipe to format table. That’s where the custom formatting comes into play. This .format.ps1xml file also includes custom formatting for the next function we’ll look at.

<?xml version="1.0" encoding="utf-8" ?>
<Configuration>
    <ViewDefinitions>
        <View>
            <Name>MrNS.Token</Name>
            <ViewSelectedBy>
                <TypeName>MrNS.Token</TypeName>
            </ViewSelectedBy>
            <TableControl>
                <TableHeaders>
                     <TableColumnHeader>
                        <Width>12</Width>
                    </TableColumnHeader>
                    <TableColumnHeader>
                        <Width>15</Width>
                    </TableColumnHeader>
                    <TableColumnHeader>
                        <Width>35</Width>
                    </TableColumnHeader>
                    <TableColumnHeader>
                        <Width>24</Width>
                    </TableColumnHeader>
                    <TableColumnHeader>
                        <Width>24</Width>
                    </TableColumnHeader>
                </TableHeaders>
                <TableRowEntries>
                    <TableRowEntry>
                        <TableColumnItems>
                            <TableColumnItem>
                                <PropertyName>UserName</PropertyName>
                            </TableColumnItem>
                            <TableColumnItem>
                                <PropertyName>Source_IP</PropertyName>
                            </TableColumnItem>
                            <TableColumnItem>
                                <PropertyName>Session_Token</PropertyName>
                            </TableColumnItem>
                            <TableColumnItem>
                                <PropertyName>CreateTime</PropertyName>
                            </TableColumnItem>
                            <TableColumnItem>
                                <PropertyName>ModifiedTime</PropertyName>
                            </TableColumnItem>
                        </TableColumnItems>
                    </TableRowEntry>
                 </TableRowEntries>
            </TableControl>
        </View>
        <View>
            <Name>MrNS.Volume</Name>
            <ViewSelectedBy>
                <TypeName>MrNS.Volume</TypeName>
            </ViewSelectedBy>
            <TableControl>
                <TableHeaders>
                     <TableColumnHeader>
                        <Width>25</Width>
                    </TableColumnHeader>
                    <TableColumnHeader>
                        <Width>10</Width>
                    </TableColumnHeader>
                    <TableColumnHeader>
                        <Width>10</Width>
                    </TableColumnHeader>
                    <TableColumnHeader>
                        <Width>26</Width>
                    </TableColumnHeader>
                    <TableColumnHeader>
                        <Width>18</Width>
                    </TableColumnHeader>
                    <TableColumnHeader>
                        <Width>10</Width>
                    </TableColumnHeader>
                </TableHeaders>
                <TableRowEntries>
                    <TableRowEntry>
                        <TableColumnItems>
                            <TableColumnItem>
                                <PropertyName>Name</PropertyName>
                            </TableColumnItem>
                            <TableColumnItem>
                                <PropertyName>Size</PropertyName>
                            </TableColumnItem>
                            <TableColumnItem>
                                <PropertyName>vol_state</PropertyName>
                            </TableColumnItem>
                            <TableColumnItem>
                                <PropertyName>perfpolicy_name</PropertyName>
                            </TableColumnItem>
                            <TableColumnItem>
                                <PropertyName>thinly_provisioned</PropertyName>
                            </TableColumnItem>
                            <TableColumnItem>
                                <PropertyName>block_size</PropertyName>
                            </TableColumnItem>
                        </TableColumnItems>
                    </TableRowEntry>
                 </TableRowEntries>
            </TableControl>
        </View>
    </ViewDefinitions>
</Configuration>

You could also add custom formatting so piping to Format-List would only return specific properties unless all properties were explicitly requested either with Format-Table -Property * or Select-Object -Property *. An example of how to write custom formatting for list views can be found in my blog article about My Solution: August 2015 PowerShell Scripting Games Puzzle.

I also added a custom types file which adds a human readable date for the session token creation and modified time stamps. The value that the API returns is the number of seconds since January 1, 1970. The .types.ps1xml file simply adds a couple of script properties without having to manually add them to the function itself which seemed to be a cleaner solution.

<Types>
    <Type>
      <Name>MrNS.Token</Name>
      <Members>
        <ScriptProperty>
          <Name>CreateTime</Name>
          <GetScriptBlock>
            (Get-Date -Date '1/1/1970').AddSeconds($this.creation_time)
          </GetScriptBlock>
        </ScriptProperty>
      </Members>
    </Type>
    <Type>
      <Name>MrNS.Token</Name>
      <Members>
        <ScriptProperty>
          <Name>ModifiedTime</Name>
          <GetScriptBlock>
            (Get-Date -Date '1/1/1970').AddSeconds($this.last_modified)
          </GetScriptBlock>
        </ScriptProperty>
      </Members>
    </Type>
</Types>

To me, the really cool function is the one I wrote to retrieve a list of volumes:

#Requires -Version 4.0 -Module TunableSSLValidator
function Get-MrNSVolume {

<#
.SYNOPSIS
    Retrieves information for volumes on a Nimble SAN.

.DESCRIPTION
    Get-MrNSVolume is an advanced function that retrieves information for volumes on a Nimble SAN.
    The TunableSSLValidator module is required since Nimble uses an untrusted SSL certificate. It can
    be found on GitHub: https://github.com/Jaykul/Tunable-SSL-Validator

.PARAMETER Group
    The DNS name or IP address of the Nimble group. The default is the group that you've already connected
    to using the Connect-MrNSGroup function.

.PARAMETER Port
    The port number for the Nimble REST API. The default value is 5392. This parameter is hidden and
    provided only as a means to easily change the port number if the API ever changes.

.PARAMETER Name
    Name of the volume.

.PARAMETER Id
    Identifier for the volume.

.EXAMPLE
     Get-MrNSVolume -Name Volume001

.EXAMPLE
     Get-MrNSVolume -Id '07204756105a0139c1000000000000000000000009'

.INPUTS
    None

.OUTPUTS
    MrNS.Volume

.NOTES
    Author:  Mike F Robbins
    Website: http://mikefrobbins.com
    Twitter: @mikefrobbins
#>

    [CmdletBinding(DefaultParameterSetName='Name')]
    param (
        [Parameter(DontShow)]
        [ValidateNotNullOrEmpty()]
        [string]$Group = $array,

        [Parameter(DontShow)]
        [ValidateNotNullOrEmpty()]
        [int]$Port = 5392,

        [Parameter(ParameterSetName='Name')]
        [string]$Name,

        [Parameter(ParameterSetName='Id')]
        [string]$Id
    )

    $Uri = "https://$($Group):$($Port)"

    $Params = @{
        Header = @{'X-Auth-Token' = $session_token}
        Method = 'Get'
        Insecure = $true
    }

    if ($PSBoundParameters.Name){

        $Params.Uri = "$Uri/v1/volumes?name=$Name"

    }
    elseif ($PSBoundParameters.Id) {

        $Params.Uri = "$Uri/v1/volumes?id=$Id"
    }
    else {

        $Params.Uri = "$Uri/v1/volumes"

    }

    $Volumes = (TunableSSLValidator\Invoke-RestMethod @Params).data

    foreach ($Volume in $Volumes.id){

        $Params.Uri = "$Uri/v1/volumes/$Volume"

        $Result = (TunableSSLValidator\Invoke-RestMethod @Params).data
        $Result.PSTypeNames.Insert(0,'MrNS.Volume')

        Write-Output $Result

    }

}

Do you see how splatting is used to dynamically build the commands and how it’s used to reduce the amount of redundancy and minimize the amount of code you have to write?

The functions shown in this blog article are part of my MrNS PowerShell module which can be downloaded from my Nimble repository on GitHub.

ยต