Use PowerCLI for vCloud Director (vCD) and direct REST calls from within the same login session

With PowerCLI’s ConnectCI-Server CmdLet it is pretty easy to login to a vCD host. And for most of the time the supplied CmdLets are sufficient enough to solve the problems we deal with it daily. However, sometimes we would like to make specific REST calls to vCD directly (for example when the Searc-Cloud CmdLet is not doing what we want). In this case you would normally make another connection to vCD. This commonly involves saving a cookie or session state, but moreover requries you to know how to make the login call. This seems an unneccessary burden, because you already logged in successfully earlier on via Connect-CiServer.

But there is help. When you save the session of your PowerCLI logon you will see it contains several properties with SessionSecret and SessionId looking quite promising:

PS > $SessionVcd | fl * -force
IsConnected : True
ServiceUri : https://vcloud.sharedop.com/api/
SessionId : rawCookieText
User : edgar
RefCount : 1
Org : System
Port : 443
Version : 1.5.1.622844
ExtensionData : VMware.VimAutomation.Cloud.Views.VCloud
Id : /CIServer=edgar:system@vcloud.sharedop.com:443/
Name : vcloud.sharedop.com
Client : /CIServer=edgar:system@vcloud.sharedop.com:443/
Uid : /CIServer=edgar:system@vcloud.sharedop.com:443/CIServer=&slash;CIServer&eq;edgar:system@vcloud.sharedop.com:443&slash;/
Description : Represents a vCloud server session.
Href : https://vcloud.sharedop.com:443/api/
SessionSecret : rawCookieText

With that you can perform an actual query to vCD without an extra login:

# create WebClient
$wc = New-Object System.Net.WebClient;

# extract version number from logged in PowerCLI session
$SessionVcd.Version -match '^(\d\.\d)\..+$'
$HeaderAcceptValue = [string]::Format("application/*+xml;version={0}", $Matches[1]);
$HeaderCookieValue = [string]::Format("vcloud-token={0}; Secure; Path=/", $SessionVcd.SessionSecret);
# add headers for request
$wc.Headers.Add("Accept", $HeaderAcceptValue);
$wc.Headers.Add("Cookie", $HeaderCookieValue);

# perform request
$aBytes = $wc.DownloadData($SessionVcd.ServiceUri.AbsoluteUri);

# transform to xml document
$xmlResult = [xml] [System.Text.Encoding]::ASCII.GetString($aBytes);

# extract org link (or whatever)
$xmlResult.Session.link[0].href
https://vcloud.sharedop.com/api/org/

When using the Invoke-RestMethod (available from PS v3) you have to fill the Session object first. Adding headers will not be enough. Note: The WebClient used in the example above is also available on PS v2.

As a side note for the WebClient object: You have to add the “Accept”-Header again for every call you make. The “Cookie” is persistent on the object, though.

As you can see it is pretty easy to re-use the log in from PowerCLI without having to worry about the version and login details of the vCD REST API. As an additional benefit for long running tasks. In case you timeout on a session all you need to do is refresh one connection and take the properties from the returned session object instead of doing two re-logins.

Below you find an example of a Cmdlet function that takes a VCD session and generates a REST call against vCD:

function Invoke-VcdRestCall {
<#

.SYNOPSIS

Performs a REST call against a vCD host and returns the XML result set.



.DESCRIPTION

Performs a REST call against a vCD host and returns the XML result set.



.OUTPUTS

This Cmdlet returns an XML document on success. On failure it returns $null.



.INPUTS

See PARAMETER section for a description of input parameters.



.PARAMETER CIServer

The vCD host to execute the REST call against. Default is '$global:DefaultCIServers[0]'.



.PARAMETER Method

The HTTP method of the REST call. Default is 'GET'. Possible values: 'GET', 'POST', 'DELETE'. 'PUT'.

Alias: m


.PARAMETER Api

The command part of the REST call. Default is 'query'. For possible values see the vCD REST reference.

Alias: a


.PARAMETER QueryParameters

The QueryString part of the REST call. For possible values see the vCD REST reference.

Alias: q


.PARAMETER Body

Optional body of the REST call when using a POST or PUT operation/method. Default is '$null'. For possible values see the vCD REST reference.

Alias: b


.EXAMPLE

Gets all possible 'query' operations of the vCD REST query service.

$xmlResponse = Invoke-VcdRestCall;
$xmlResponse.QueryList.Link;


.EXAMPLE

Gets all vCD Cells.

$xmlResponse = Invoke-VcdRestCall -Api "query" -QueryParameters "type=cell";
$xmlResponse.QueryResultRecords.CellRecord;


.LINK

Online Version: http://dfch.biz/PS/vCD/Utilities



.NOTES

Requires Powershell v3.

Requires module 'biz.dfch.PS.System.Logging'.
Requires a PowerCLI session to VCD.

#>
	[CmdletBinding(
		HelpURI='http://dfch.biz/PS/vCD/Utilities/Invoke-VcdRestCall'
    )]
	[OutputType([xml])]
  Param (
		[Parameter(Mandatory = $false, Position = 2)]
		[alias("s")]
		$CIServer = $global:DefaultCIServers[0]
		,
		[ValidateSet('GET', 'POST', 'PUT', 'DELETE')]
		[Parameter(Mandatory = $false, Position = 1)]
		[alias("m")]
		[string] $Method = 'GET'
		, 
		[Parameter(Mandatory = $false, Position = 0)]
		[alias("a")]
		[string] $Api = 'query'
		,
		[Parameter(Mandatory = $false, Position = 3)]
		[alias("q")]
		[string] $QueryParameters = $null
		, 
		[Parameter(Mandatory = $false, Position = 4)]
		[alias("b")]
		[string] $Body = $null 
	) # Param
	BEGIN {
		$datBegin = [datetime]::Now;
		[string] $fn = $MyInvocation.MyCommand.Name;
		Log-Debug -fn $fn -msg "CALL. Api: '$Api'." -fac 1;
	}
	PROCESS {
		[boolean] $fReturn = $false;
		$retNull = $null;

		try {
			# Parameter validation
			if( !$CIServer -or ($CIServer.GetType().FullName -ne "VMware.VimAutomation.Cloud.Impl.V1.CIServerImpl") ) {
				Log-Error $fn "Invalid input parameter type specified: CIServer [$($CIServer.GetType().FullName)]. Aborting ...";
				throw($gotoFailure);
			} # if
			if( !$CIServer.IsConnected ) {
				Log-Error $fn "CIServer '$($CIServer.Name)' is not connected. Reconnect session before trying again. Aborting ...";
				throw($gotoFailure);
			} # if
			if(!$Api) {
				Log-Error $fn "Invalid or empty input parameter specified: Api. Aborting ...";
				throw($gotoFailure);
			} # if

			# create WebClient
			$SessionVcd = $CIServer;
			$wc = New-Object System.Net.WebClient;
			# extract version number from logged in PowerCLI session
			$fReturn = $SessionVcd.Version -match '^(\d\.\d)\..+$';
			if(!$fReturn) {
				Log-Error $fn "CIServer version could not be matched: '$($SessionVcd.Version)'. Aborting ...";
			} # if
			$HeaderAcceptValue = [string]::Format("application/*+xml;version={0}", $Matches[1]);
			$HeaderCookieValue = [string]::Format("vcloud-token={0}; Secure; Path=/", $SessionVcd.SessionSecret);
			# add headers for request
			$null = $wc.Headers.Add("Accept", $HeaderAcceptValue);
			$null = $wc.Headers.Add("Cookie", $HeaderCookieValue);

			[string] $Uri = [string]::Format("{0}{1}?{2}", $SessionVcd.ServiceUri.AbsoluteUri, $Api, $QueryParameters);
			Log-Debug $fn "Invoking '$Method' '$Uri' ...";
			[string] $response = '';
			if('GET'.Equals($Method.ToUpper()) ) {
				$response = $wc.DownloadString($Uri);
			} else {
				$response = $wc.UploadString($Uri, $Method.ToUpper(), $Body);
			} # if
			$null = $wc.Dispose();
			# transform to xml document
			$xmlResult = [xml] $response;
			$OutputParameter = $xmlResult.Clone();
			
		} # try
		catch {
			if($gotoSuccess -eq $_.Exception.Message) {
					$fReturn = $true;
			} else {
				[string] $ErrorText = "catch [$($_.FullyQualifiedErrorId)]";
				$ErrorText += (($_ | fl * -Force) | Out-String);
				$ErrorText += (($_.Exception | fl * -Force) | Out-String);
				$ErrorText += (Get-PSCallStack | Out-String);
				
				if( ($_.Exception.InnerException) -and ([System.Net.WebException] -eq ($_.Exception.InnerException.GetType())) ) {
					Log-Critical $fn "Operation '$Method' '$Api' with CIServer '$($CiServer.Name)' FAILED [$_].";
					Log-Debug $fn $ErrorText -fac 3;
				} # [System.Net.WebException]
				else {
					Log-Error $fn $ErrorText -fac 3;
					if($gotoFailure -ne $_.Exception.Message) { Write-Verbose ("$fn`n$ErrorText"); }
				} # other exceptions
				$fReturn = $false;
				$OutputParameter = $null;
			} # !$gotoSuccess
		} # catch
		finally {
			# Clean up
		} # finally
		return $OutputParameter;
  } # PROCESS
	END {
		$datEnd = [datetime]::Now;
		Log-Debug -fn $fn -msg "RET. fReturn: [$fReturn]. Execution time: [$(($datEnd - $datBegin).TotalMilliseconds)]ms. Started: [$($datBegin.ToString('yyyy-MM-dd HH:mm:ss.fffzzz'))]." -fac 2;
	} # END
} # function
Set-Alias -Name Invoke-VcdCommand -Value Invoke-VcdRestCall;
Export-ModuleMember -Function Invoke-VcdRestCall -Alias Invoke-VcdCommand;

As a side note: you might want to use the same cookie value for access to vCD Administration GUI (via vcloud_session_id) as well. For further information see Login to vCloud Director 1.5 with a generated Cookie.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: