The Citrix XenApp Essentials Service UI provides a great way to create and manage customer catalogs. However, automated scripts can make it easy to perform some repetitive tasks around catalog management. One instance of such a task is updating the catalog image. Many customers update a catalog image often by applying Windows updates and security patches. Do you frequently update your catalog image often? If yes, we have some good news for you.

We are excited to announce the Tech Preview of Citrix XenApp Essentials Service REST APIs. The individual APIs can be browsed and tried out at Citrix XenApp Essentials Service REST APIs – Tech Preview

Complete API documentation with a detailed explanation of all REST APIs request and response is available here.

NOTE: The following code sample assumes that you already have a Citrix Cloud account and know how to get your customerId, clientId and clientSecret for API access. If you need help with that, please refer Getting Started with Citrix Cloud APIs.

Step 1: Get your Citrix Cloud bearer token

Add the bearer token in the Authorization request header for all API calls for authentication.


function GetBearerToken {
  param (
    [Parameter(Mandatory=$true)]
    [string] $clientId,
    [Parameter(Mandatory=$true)]
    [string] $clientSecret
  )

  $postHeaders = @{"Content-Type"="application/json"}
  $body = @{
    "ClientId"=$clientId;
    "ClientSecret"=$clientSecret
  }
  $trustUrl = "https://trust.citrixworkspacesapi.net/root/tokens/clients"

  $response = Invoke-RestMethod -Uri $trustUrl -Method POST -Body (ConvertTo-Json $body) -Headers $postHeaders
  $bearerToken = $response.token

  return $bearerToken;
}

$clientId = "b959ac67-..." #Replace with your clientId
$clientSecret = "D8..." #Replace with your clientSecret

$bearerToken = GetBearerToken $clientId $clientSecret

Step 2: Get your Site ID

Get the ID of the site where Citrix hosts your resources.


function GetSites {
  param (
    [Parameter(Mandatory=$true)]
    [string] $customerId,
    [Parameter(Mandatory=$true)]
    [string] $bearerToken
  )
  $requestUri = [string]::Format("https://catalogs.apps.cloud.com/{0}/sites", $customerId)
  $headers = @{"Accept"="application/json";
               "Authorization"="CWSAuth bearer=$bearerToken"}

  $response = Invoke-RestMethod -Uri $requestUri -Method GET -Headers $headers
  return $response
}
#Call the GetSites function to get the site ID
$sites = GetSites $customerId $bearerToken
$siteId = $sites.Sites[0].Id

Step 3: Add the master image to your account

Add the new master image to update the catalog to your account.


function AddMasterImage {
  param (
    [Parameter(Mandatory=$true)]
    [string] $customerId,
    [Parameter(Mandatory=$true)]
    [string] $siteId,
    [Parameter(Mandatory=$true)]
    [string] $bearerToken,
    [Parameter(Mandatory=$true)]
    [string] $jsonBody
  )
  $requestUri = [string]::Format("https://catalogs.apps.cloud.com/{0}/{1}/images", $customerId, $siteId)
  $headers = @{"Accept"="application/json";
               "Content-Type"="application/json"
               "Authorization"="CWSAuth bearer=$bearerToken"}

  $response = Invoke-RestMethod -Uri $requestUri -Method POST -Headers $headers -Body $jsonBody
  return $response
}
#Post body to pass to the API
$body = @{
  "Name" = "Finance Image";
  "ResourceGroup" = "FinanceRG";
  "StorageAccount" = "xaensfa5edge1gu4o1";
  "SubscriptionId" = "c38c7a5c-...";
  "Validate" = $true;
  "VhdUrl" = "https://xaensfa5edge1gu4o1.blob.core.windows.net/vhds/XAEnsfa5-Findisk0.vhd"
}

#Call the API to Add the master image
$imageId = AddMasterImage $customerId $siteId $bearerToken (ConvertTo-Json $body)

 Step 4: Wait for the master image to finish processing

After adding the image, XenApp Essentials service performs certain validations on the image to ensure that it is ready. Validations like the supported OS version, supported Citrix Server VDA, etc are done as part of this processing. Wait until all the validations are complete and the service indicates that the image is ready for use. If the image state is Failed after the processing, then please correct the error and rerun the script.


function GetMasterImage {
  param (
    [Parameter(Mandatory=$true)]
    [string] $customerId,
    [Parameter(Mandatory=$true)]
    [string] $siteId,
    [Parameter(Mandatory=$true)]
    [string] $bearerToken,
    [Parameter(Mandatory=$true)]
    [string] $imageId
  )
  $requestUri = [string]::Format("https://catalogs.apps.cloud.com/{0}/{1}/images/{2}", $customerId, $siteId, $imageId)
  $headers = @{"Accept"="application/json";
               "Authorization"="CWSAuth bearer=$bearerToken"}

  $response = Invoke-RestMethod -Uri $requestUri -Method GET -Headers $headers
  return $response
}
#wait until the image finishes processing
while ($true)
{
  $image = GetMasterImage $customerId $siteId $bearerToken $imageId
  if ($image.State -eq "Ready")
  {
    Write-Host "The image is ready to be used by the catalog"
    break;
  }
  elseif ($image.State -eq "Failed")
  {
    Write-Host "Image validation failed with error $($image.Status). Please correct the error and add the image again"
    break;
  }
  Start-Sleep -Seconds 60
}

 Step 5: Get the ID of the catalog to update

Skip this step if you already know the catalog ID.


function GetAllCatalogs {
  param (
    [Parameter(Mandatory=$true)]
    [string] $customerId,
    [Parameter(Mandatory=$true)]
    [string] $siteId,
    [Parameter(Mandatory=$true)]
    [string] $bearerToken
  )
  $requestUri = [string]::Format("https://catalogs.apps.cloud.com/{0}/{1}/catalogs", $customerId, $siteId)
  $headers = @{"Accept"="application/json";
               "Authorization"="CWSAuth bearer=$bearerToken"}

  $response = Invoke-RestMethod -Uri $requestUri -Method GET -Headers $headers
  return $response
}
#Get all the catalogs and select the one you are interested in
$catalogs = GetAllCatalogs $customerId $siteId $bearerToken

$catalogName = "your catalog name"
$catalogToUpdate = $catalogs.Catalogs | where {$_.Name -eq $catalogName}
$catalogId = $catalogToUpdate.Id

Step 6: Update the catalog with your new master image

Once the master image is in Ready state, update the catalog using the image.


function UpdateCatalogImage {
  param (
    [Parameter(Mandatory=$true)]
    [string] $customerId,
    [Parameter(Mandatory=$true)]
    [string] $siteId,
    [Parameter(Mandatory=$true)]
    [string] $bearerToken,
    [Parameter(Mandatory=$true)]
    [string] $catalogId,
    [Parameter(Mandatory=$true)]
    [string] $jsonBody
  )
  $requestUri = [string]::Format("https://catalogs.apps.cloud.com/{0}/{1}/catalogs/{2}/updateimage", $customerId, $siteId, $catalogId)
  $headers = @{"Accept"="application/json";
               "Content-Type"="application/json"
               "Authorization"="CWSAuth bearer=$bearerToken"}

  $response = Invoke-RestMethod -Uri $requestUri -Method POST -Headers $headers -Body $jsonBody
  return $response
}
#Post body to pass to the API
$body = @{
  "TemplateId" = $imageId;
  "CitrixPrepared" = $false;
  "VdaUpdateDelay" = "60"
}
#Update the catalog with the new master image
$response = UpdateCatalogImage $customerId $siteId $bearerToken $catalogId (ConvertTo-Json $body)

Step 7: Wait for the catalog update to complete

XenApp Essentials service updates the catalog VDAs with the new master image. This will take some time. Keep polling the state of the catalog until the processing is complete.


function GetCatalog {
  param (
    [Parameter(Mandatory=$true)]
    [string] $customerId,
    [Parameter(Mandatory=$true)]
    [string] $siteId,
    [Parameter(Mandatory=$true)]
    [string] $bearerToken,
    [Parameter(Mandatory=$true)]
    [string] $catalogId
  )
  $requestUri = [string]::Format("https://catalogs.apps.cloud.com/{0}/{1}/catalogs/{2}", $customerId, $siteId, $catalogId)
  $headers = @{"Accept"="application/json";
               "Authorization"="CWSAuth bearer=$bearerToken"}
  $response = Invoke-RestMethod -Uri $requestUri -Method GET -Headers $headers
  return $response
}
#wait for the catalog update to finish
while ($true)
{
  $catalog = GetCatalog $customerId $siteId $bearerToken $catalogId
  if ($catalog.State -eq "Active" -or $catalog.State -eq "InputRequired")
  {
    Write-Host "Catalog update complete successfully"
    break;
  }
  elseif ($catalog.State -eq "Error")
  {
    Write-Host "Catalog update failed with error $($catalog.StatusMessage). Please correct the error and try again"
    break;
  }
  else
  {
    Write-Host "Catalog state is $($catalog.State) and substate is $($catalog.SubState)"
  }
  Start-Sleep -Seconds 60
}

Download the sample script discussed in this blog here.

Disclaimer
This software / sample code is provided to you “AS IS” with no representations, warranties or conditions of any kind. You may use, modify and distribute it at your own risk. CITRIX DISCLAIMS ALL WARRANTIES WHATSOEVER, EXPRESS, IMPLIED, WRITTEN, ORAL OR STATUTORY, INCLUDING WITHOUT LIMITATION WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NONINFRINGEMENT. Without limiting the generality of the foregoing, you acknowledge and agree that (a) the software / sample code may exhibit errors, design flaws or other problems, possibly resulting in loss of data or damage to property; (b) it may not be possible to make the software / sample code fully functional; and (c) Citrix may, without notice or liability to you, cease to make available the current version and/or any future versions of the software / sample code. In no event should the software / code be used to support of ultra-hazardous activities, including but not limited to life support or blasting activities. NEITHER CITRIX NOR ITS AFFILIATES OR AGENTS WILL BE LIABLE, UNDER BREACH OF CONTRACT OR ANY OTHER THEORY OF LIABILITY, FOR ANY DAMAGES WHATSOEVER ARISING FROM USE OF THE SOFTWARE / SAMPLE CODE, INCLUDING WITHOUT LIMITATION DIRECT, SPECIAL, INCIDENTAL, PUNITIVE, CONSEQUENTIAL OR OTHER DAMAGES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. Although the copyright in the software / code belongs to Citrix, any distribution of the code should include only your own standard copyright attribution, and not that of Citrix. You agree to indemnify and defend Citrix against any and all claims arising from your use, modification or distribution of the code.