Jump to content
Welcome to our new Citrix community!

Citrix Image Portability Service - Usage with PowerShell, .NET, and REST-API

  • Contributed By: Gerhard Krenn Special Thanks To: Daniel Lazar, Michael Dooley, Steve Beals, Steven Gallagher

Overview

Citrix developed the Citrix Image Portability Service (IPS) to simplify moving workloads between different resource locations and hypervisor platforms. With IPS you can even move your workloads between on-premises and public cloud environments.

Citrix Image Portability Service provides Citrix administrators with a simple workflow to manage workloads between on-premises and public cloud platforms like:

  • Microsoft Azure
  • Google Cloud Platform (GCP)
  • AWS (AWS)
  • VMware vSphere
  • Citrix XenServer
  • Nutanix

Developed using App Layering cross-platform technology, the Citrix Image Portability Service uses Citrix DaaS REST-APIs to migrate on-premises Machine Creation or Provisioning Services Images.

The Image Portability workflow is the framework for a migration of an Image from your on-premises location to your public cloud subscription.

After exporting your Image, Image Portability Service helps you transfer the Image to your public cloud subscription and prepare it to run.

Finally, Citrix Provisioning or Machine Creation Services provisions the Image in your public cloud subscription.

The product documentation can be found at https://docs.citrix.com/en-us/citrix-daas/migrate-workloads.html.

Scope

In this guide, we show how to migrate workflows from on-premises (VMware vSphere 8) to public cloud (Microsoft Azure and AWS).

We use three methods:

  • Pure API calls using Postman
  • PowerShell (PoSH)
  • A self-written Windows application that encapsulates all necessary API calls and PowerShell scripts into a GUI

Important Note:

The self-written .NET application only shows examples how to implement the API calls and PowerShell scripts into an application.

No warranty, no liability, and no support of any kind are given for the application.

Conceptual architecture

The Citrix Image Portability Service (IPS) is a REST-API with all the semantics and requirements of other Citrix DaaS and Virtual Apps and Desktops APIs.

The headers and authentication work the same as the other Citrix APIs do.

Citrix IPS relies on the Connector Appliance for Cloud Services to communicate with Citrix Cloud. IPS uses provisioning technology to create and manage resources in the resource location where the Connector Appliance is installed. More info: https://docs.citrix.com/en-us/tech-zone/learn/tech-briefs/Image-portability-service.html.

poc-guides_ips-components.png

Image Portability Service components

  • Citrix Cloud services
  • Citrix Credential Wallet
  • Citrix Connector Appliance
  • Compositing Engine VM
  • PowerShell modules

Citrix Cloud services

The Citrix Cloud Services API is a REST-API service that interacts with the Image Portability Service. Using the REST-API service, you can create and monitor Image Portability jobs.

For example, you make an API call to start an Image Portability job, such as to export a disk, and then make calls to get the status of the job.

Citrix Credential Wallet

The Citrix Credential Wallet service securely manages system credentials, allowing the Image Portability Service to interact with your assets.

For example, when exporting a disk from vSphere to an SMB share, the Image Portability Service requires credentials to open a connection to vSphere and to the SMB share to write the disk. The credentials are stored in the Credential Wallet and the Image Portability Service can retrieve and use those credentials. Create all necessary credentials in the right format before use IPS.

This service gives you the ability to fully manage your credentials. The Cloud Services API acts as an access point, giving you the ability to create, update, and delete credentials.

Compositing Engine

The Compositing Engine is the workhorse of the Image Portability Service. The Compositing Engine (CE) is a single VM created at the start of an Image Portability job. The CE is created in the jobs target hypervisor/hyperscaler.

For example, when exporting a disk from vSphere, the job creates the CE on the vSphere server. When a prepare job runs in Azure, AWS, or Google Cloud, it creates the CE in Azure, AWS, or Google respectively. The CE mounts your disk to itself, and then does the necessary manipulations to the disk. Upon completion of the preparation or export job, the CE VM and all of its components are deleted.

Connector Appliance

The Connector Appliance runs in your environment and acts as a controller for individual jobs. It receives job instructions from the Cloud service, and creates and manages the Compositing Engine VMs.

The Connector Appliance VM acts as a single, secure point of communication between the Citrix Cloud services. Deploy one or more Connector Appliances in each of your Resource Locations.

By co-locating the Connector Appliance and the Compositing Engine, the deployment’s security posture increases greatly. The Connector Appliance keeps all components and communications within the same Resource Location. It needs access to the following URLs to prepare Images in the Image Portability Service:

*.layering.cloud.com
credentialwallet.citrixworkspaceapi.net
graph.microsoft.com
login.microsoftonline.com
management.azure.com
*.blob.storage.azure.net

PowerShell modules

We provide a collection of PowerShell modules for use within scripts as a starting point to develop your own custom automation. The supplied modules are supported as is, but you can modify them if necessary for your deployment.

The PowerShell automation uses supplied configuration parameters to compose a REST call to the Citrix Cloud API service to start the job. It provides you with periodic updates as the job progresses.

If you want to develop your own automation solution, you can make calls to the cloud service directly using your preferred programming language. See the API portal for detailed information about configuring and using the Image Portability Service REST endpoints and PowerShell modules https://developer.cloud.com/citrixworkspace/citrix-daas/Image-portability-service/docs/overview.

To use the PowerShell scripts, you need the following:

  • The latest version of PowerShell
  • Connectivity to the Microsoft PowerShell Gallery to download the required PowerShell libraries
Install-Module -Name PowerShellGet -Force -Scope CurrentUser -AllowClobber

Install-Module -Name "Citrix.Workloads.Portability","Citrix.Image.Uploader" -Scope CurrentUser
Update-Module -Name "Citrix.Workloads.Portability","Citrix.Image.Uploader" -Force

Install-Module -Name Az.Accounts -Scope CurrentUser -AllowClobber -Force
Install-Module -Name Az.Compute -Scope CurrentUser -AllowClobber -Force

Install-Module -Name VMware.PowerCLI -Scope CurrentUser -AllowClobber -Force -SkipPublisherCheck

Install-Module -Name Amazon AWS.Tools.Installer
Install-Amazon AWSToolsModule Amazon AWS.Tools.EC2,Amazon AWS.Tools.S3

All PowerShell cmdlets have built-in help containing full syntax and examples:

Get-Help Start-IpsVsphereExportJob -Full
PS C:\TACG> Get-Help Start-IpsVsphereExportJob

NAME
Start-IpsVsphereExportJob

OVERVIEW
Starts an Image Portability Service job to export an Image from Vsphere.

SYNTAX
Start-IpsVsphereExportJob -CustomerId <String> -SmbHost <String> [-SmbPort <String>] -SmbShare <String> [-SmbPath <String>] -SmbDiskName <String> [-SmbDiskFormat <String>] -SmbCwId <String> [-Deployment <String>] -ResourceLocationId <String> -VsphereCwSecretId <String> -VsphereHost <String> [-VspherePort <Int32>][-VsphereSslCaCertificateFilePath <String>] [-VsphereSslCaCertificate <String>] [-VsphereSslFingerprint <String>] [-VsphereSslNoCheckHostname <Boolean>] -VsphereDataCenter <String> -VsphereDataStore <String> [-VsphereResourcePool <String>] -VsphereNetwork <String> [-VsphereHostSystem <String>] [-VsphereCluster <String>] -SourceDiskName <String> [-AssetsId <String>] [-Tags <Hashtable>] [-Timeout <Int32>] [-Prefix <String>] [-JobDebug <Boolean>] [-Flags <String[]>] [-DryRun <Boolean>] [-SecureClientId <String>] [-SecureSecret <String>] [-LogFileDir <String>] [-LogFileName <String>] [-OverwriteLog] [-Force] [<CommonParameters>]

Start-IpsVsphereExportJob -ConfigJsonFile <String> [-SecureClientId <String>] [-SecureSecret <String>] [-LogFileDir <String>] [-LogFileName <String>] [-OverwriteLog] [-Force] [<CommonParameters>]

DESCRIPTION
Starts an Image Portability Service job to export an Image from Vsphere to a virtual disk file on a SMB fileshare.
Start-IpsVspherePublishJob is an alias for this cmdlet.

LINKS

REMARKS
To open the examples, type: "get-help Start-IpsVsphereExportJob -examples".
Obtain more information by using: "get-help Start-IpsVsphereExportJob -detailed".
Technical information can be be obtained by using: "get-help Start-IpsVsphereExportJob -full".

Requirements

  • A Citrix Connector Appliance in all resource locations where IPS will be used.
  • A Windows (SMB) file share must be locally accessible to any export, prepare, and publish job.
  • A valid Citrix Cloud Customer ID and Citrix DaaS entitlement.
  • On-premises master Machine Create Services (MCS) or Provisioning Services (PVS) Image.
  • Access to public cloud resource location.
  • A Citrix Machine Catalog Image - IPS requires using Images that have one of the following tested configurations:
    • Windows Server 2016, 2019, and 2022H2
    • Windows 10 or 11
    • Provisioned using Machine Creation Services or Citrix Provisioning
    • Citrix Virtual Apps and Desktops VDA version 1912CU6, 1912CU7, 2203CU1, 2203CU2, 2212, 2303, 2305, 2308
    • Citrix PVS Agent version 1912CU6, 1912CU7, 2203CU1, 2203CU2, 2212, 2303, 2305, 2308
    • Remote Desktop Services enabled for console access in Azure

Image Portability service supports the following hypervisors and cloud platforms

Source platforms:

  • VMware vSphere 7.0 and 8.0
  • Citrix Hypervisor/XenServer 8.2
  • Nutanix Prism Element 3.x
  • Microsoft Azure
  • Google Cloud Platform

Destination platforms:

  • VMware 8.0
  • Microsoft Azure
  • AWS
  • Google Cloud Platform

For using Postman

Download the Postman app for API calls: https://www.postman.com/downloads/

For using PowerShell

Download the Remote PowerShell kit: https://docs.citrix.com/en-us/citrix-daas/sdk-api.html#citrix-virtual-apps-and-desktops-remote-powershell-sdk

Install-Module -Name "Citrix.Workloads.Portability","Citrix.Image.Uploader"
Add-PSSnapin Citrix.*
Get-Module -ListAvailable "Citrix.*"

Folder: C:\Program Files\WindowsPowerShell\Modules

 ModuleType Version    Name                             ExportedCommands
 ---------- -------    ----                             ----------------
 Script     2.1.11.0   Citrix.Image.Uploader            {Copy-ToAzDisk, Copy-ToAwsDisk, Get-VhdSize, Get-VhdConten...
 Script     2.3.1      Citrix.Workloads.Portability     {Start-IpsAwsPrepareJob, Start-IpsVsphereExportJob, Start-...

For using the Windows .NET application

  • Download and install the DaaS Remote PowerShell kit
  • Download and install the latest version of PowerShell
  • Download and install the latest version of Azure PowerShell
  • Download and install .NET 7.0 Core
  • Download and install Chilkatsoft Chilkat .NET Core

Important Note:

The self-written .NET application only shows examples how to implement the API calls and PowerShell scripts into an application. No warranty, no liability, and no support of any kind are given for the application.

The .NET application shows the possibility to automate workflows by invoking REST-API calls and PowerShell scripts. The source code implements all 4 steps of the Image Migration Workflow.

Complete Citrix Cloud Prerequisites

  1. Furthermore, some account information is necessary to obtain – the most important one is the Customer ID:

poc-guides_ips-cc-settings.png

  1. After logging in to Citrix Cloud, open the Account Settings window of the Cloud portal:

poc-guides_ips-cc-settings1.png

In the Account Settings window you find the Customer ID. Write it down or save it for further use.

Create an API client

API clients in Citrix Cloud are always tied to one administrator and one customer. API clients are not visible to other administrators. If you want to access to more than one customer, you must create API clients within each customer.

API clients are automatically restricted to the rights of that administrator that created it. For example, if an administrator is restricted to access only notifications, then the administrator's API clients have the same restrictions:

  • Reducing an administrator’s access also reduces the access of the API clients owned by that administrator.
  • Removing an administrator’s access also removes the administrator's API clients.
  1. To create an API client, select the Identity and Access Management option from the menu. If this option does not appear, you may not have adequate permissions to create an API client. Contact your administrator to get the required permissions:

poc-guides_ips-cc-iam1.png

  1. In the next screen select the API Access tab:

poc-guides_ips-cc-iam2.png

Name your Secure Client and click Create Client.

  1. Now a message appears that ID and Secret have been created successfully. Download or copy the Client ID and Secret to access the APIs.

poc-guides_ips-cc-iam3.png

Accessing Citrix Cloud via API calls or PowerShell

To call APIs, you must also create a Bearer token. The Bearer token is used for API authentication and authorization. Tokens can be obtained using a standard OAuth 2.0 Client Credential grant flow. For more information about OAuth 2.0 Client Credential grant, see https://tools.ietf.org/html/rfc6749#section-4.4.

To get a bearer token, make a POST call to the trust service's authentication API:

POST https://api-us.cloud.com/cctrustoauth2/{customerid}/tokens/clients

Note:

Use one of the following endpoints based on the geographical region while creating the Citrix Cloud account:

api-ap-s.cloud.com – If your Citrix Cloud account is set to the Asia Pacific South region.

api-eu.cloud.com – If your Citrix Cloud account is set to the European Union region.

api-us.cloud.com – If your Citrix Cloud account is set to the United States region.

api.citrixcloud.jp - If your Citrix Cloud account is set to the Japan region.

Obtain a Bearer token for authentication

Flow to obtain a Bearer token using the Postman app

It is important to set the URI to call and the required parameters correct: The URI must follow this syntax: For example [https://api-us.cloud.com/cctrustoauth2/{customerid}/tokens/clients] where {customerid} is your Customer ID you obtained from the Account Settings page. If your Customer ID is for example 1234567890 the URI would be [https://api-us.cloud.com/cctrustoauth2/1234567890/tokens/clients]

  1. Paste the correct URI into Postman´s address bar and select POST as the method. Verify the correct settings of the API call – please check the Headers tab to reflect the settings:

poc-guides_ips-bearer1.png

  1. The next step is to enter your API credentials into the Body of the API call. Make sure that the Parameter type is set to x-www-form-encoded:

poc-guides_ips-bearer2.png

  1. Now you are ready to call the API to get a Bearer token by pressing the Send button:

poc-guides_ips-bearer3.png

If everything is set correctly, Postman shows a Response containing a JSON-formatted file containing the Bearer token in the field access-token: The token is normally valid for 3600 seconds.

  1. If an error occured, the Response contains some hints about the error:

poc-guides_ips-bearer5.png

To get a bearer token, make a POST call to the trust service's authentication API:

POST https://api-us.cloud.com/cctrustoauth2/{customerid}/tokens/clients

Response:

{
    "token_type": "bearer",
    "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9…pZWFOBHuZ63tvGvRA",
    "expires_in": "3600"
}

Now you have a valid Bearer token for further API calls.

Flow to obtain a Bearer token using PowerShell

PS C:\TACG> $tokenUrl = 'https://api-eu.cloud.com/cctrustoauth2/1234567890/tokens/clients'
 PS C:\TACG> $response = Invoke-WebRequest $tokenUrl -Method POST -Body @{                                                                                                                       

 >> grant_type = "client_credentials"   
 >> client_id = "50XXXXXXXX-XXXXX-XXXXX-XXXXXXXXXXX7"
 >> client_secret = "8MXXXXXXXXXXXXXXXXXXX=="

 >> }

 PS C:\TACG> $token = $response.Content | ConvertFrom-Json
 PS C:\TACG> $token | Format-List

 token_type   : bearer
 access_token : J9.eyJzdWI
                ...
                BTp3KN5N_qr7Hjk-VTQy3Qp2dCId_cnagZaQleo1E98ifw2eUch1vu8tjYR-_NkA
 expires_in   : 3600

Flow to obtain a Bearer token using PowerShell

  1. All relevant data is stored in an adjacent JSON file – you can follow the guide to obtain the Customer ID and the API Client values:

poc-guides_ips-net-ccapi-variables-json.png

  1. After entering the correct values you can use the Windows application to create the Bearer token:

poc-guides_ips-net-token.png

Now you have your Bearer token for authentication and authorization. It must be included in each API-/PoSH-Call!

Image Migration Workflow

Migrating Images with the Citrix Image Portability Service consists of a 4-stage workflow.

poc-guides_ips-workflow.png

Export: The export stage exports Images from the on-premises hypervisor and preps them for upload to the public cloud resource location. This stage can include converting file system types to a common format for the cloud.

Upload: The upload stage uploads the Image to the target cloud subscription. This process is a point-to-point transfer using the configuration and credentials for the public cloud supplied by the administrator.

Prepare: The prepare stage is a complex set of steps:

  • Removal of source platform components
  • Injection of target platform components into the Image
  • Reconfiguration of the VDA
  • Rearm of the OS based on the supplied configuration properties

At the end of this phase, the Image boots once to allow Windows plug-and-play to run and configure the OS for the new platform. Once the preparation completes, the Image is ready for provisioning with MCS.

Publish: The final stage deploys the Image as a new machine catalog.

  • In the case of MCS, the Citrix Virtual Apps and Desktops Remote PowerShell SDK can automate the creation of an MCS catalog from the migrated disk.
  • In the case of PVS on a HyperScaler, the Image Portability Service provides a REST interface or a PowerShell cmdlet to publish the migrated disk directly into the PVS vDisk store.

Throughout the four stages, the Image Portability Service uses App Layering compositing engines in the background to modify the Image and drive the process.

All Citrix Image Portability workflows are based on the configuration of the source Image and provisioning targets, either MCS or PVS. The workflow chosen determines the steps required by the Image Portability Service.

Part 1: Exporting the Image - Prerequisites

Exporting the Image to an SMB file share is a multi-step process:

  • Determine the ID of the Resource Location where the Image resides
  • Add new credentials to the Credential Wallet
  • Export the Image to the SMB share

In this guide we export the Image from VMware vSphere.

The following vCenter permissions are necessary to run the IPS export disk job in a VMware environment. For current permission requirements look at the product documentation https://docs.citrix.com/en-us/citrix-daas/migrate-workloads.html#vmware-vcenter-required-permissions.

-  Cryptographic operations
    -  Direct Access

-  Datastore
    -  Allocate space
    -  Browse datastore
    -  Low level file operations
    -  Remove file

-  Folder
    -  Create folder
    -  Delete folder

-  Network
    -  Assign network

-  Resource
    -  Assign virtual machine to resource pool

-  Virtual machine
    -  Change Configuration
        -  Add existing disk
        -  Add new disk
        -  Remove disk

    -  Edit Inventory
        -  Create from existing
        -  Create new
        -  Remove

    -  Interaction
        -  Power off
        -  Power on

Determining the ID of the Resource Location using Postman

poc-guides_ips-postman-zoneid.png

GET https://api-eu.cloud.com/resourcelocations

Authorization:      CWSAuth bearer= {{Bearer-Token-Value}}
Citrix-CustomerId: {{Citrix-CustomerID}}

Response (shortened):

{
    "items": [
        {
            "id": "58110e95-XXXX-XXXX-XXXX-XXXXXXXXXX",
            "name": "The Austrian Citrix Guy - vSphere",
            "internalOnly": false,
            "timeZone": "GMT Standard Time",
            "readOnly": false
        },
        {
            "id": "5faae27c-XXXX-XXXX-XXXX-XXXXXXXXXX",
            "name": "The Austrian Citrix Guy - Amazon AWS",
            "internalOnly": false,
            "timeZone": "GMT Standard Time",
            "readOnly": false
        },
        {
            "id": "76918c03-XXXX-XXXX-XXXX-XXXXXXXXXX",
            "name": "The Austrian Citrix Guy - AzHib",
            "internalOnly": false,
            "timeZone": "GMT Standard Time",
            "readOnly": false
        },
       ...
    ]
}

Copy the value of the id field as you need it for further calls.

Determining the ID of the Resource Location using PowerShell

PS C:\TACG> Get-ConfigZone | select Name, ExternalUid 
Name                                          ExternalUid
----                                          -----------
Initial Zone                                  00000000-XXXX-XXXX-XXXX-XXXXXXXXXX
The Austrian Citrix Guy - Amazon AWS          5faae27c-XXXX-XXXX-XXXX-XXXXXXXXXX
The Austrian Citrix Guy - AzGPU               b46c6873-XXXX-XXXX-XXXX-XXXXXXXXXX
The Austrian Citrix Guy - AzHib               76918c03-XXXX-XXXX-XXXX-XXXXXXXXXX
The Austrian Citrix Guy - AzStHCI local       e344b537-XXXX-XXXX-XXXX-XXXXXXXXXX
The Austrian Citrix Guy - Azure Connectorless eec20fa2-XXXX-XXXX-XXXX-XXXXXXXXXX
The Austrian Citrix Guy - On-Prem             dc4b9f88-XXXX-XXXX-XXXX-XXXXXXXXXX
The Austrian Citrix Guy - vSphere             58110e95-XXXX-XXXX-XXXX-XXXXXXXXXX

Copy the value of the ExternalUid field as you need it for further calls.

Determining the ID of the Resource Location using the Windows .NET Application

The .NET application requests all available Resource Locations automatically. You can choose the needed one using a drop-down field.

poc-guides_ips-net-getrl.png

Add new credentials to the Credential Wallet using Postman

The correct types of Credentials are imperative for each operation - for example for Azure you need Azure-type credentials, for SMB SMB-type and so forth. The examples show the creation of UsernamePassword-type credentials.

More info about the different credential types and the creation can be found here: https://developer-docs.citrix.com/en-us/citrix-daas-service-apis/Image-portability-service/credentials.

poc-guides_ips-postman-credentials.png

POST https://api.eu.layering.cloud.com/credentials`

Authorization:      CWSAuth bearer= {{Bearer-Token-Value}}
Citrix-CustomerId: {{Citrix-CustomerID}}

Body (Raw):
{
  "id": "vspXXXXXXXX",
  "type": "UsernamePassword",
  "username": "XXXXXXXXXXXX",
  "domain": "XXXXXXXX",
  "password": "XXXXXXXXXXXXX"
}

Response (shortened):

{
    "id": "vspXXXXXXXXX",
    "type": "UsernamePassword",
    "state": "Ok",
    "createdAt": "2023-11-13T09:00:18.2371226Z",
    "updatedAt": "2023-11-13T09:00:18.2371226Z"
}

Add new credentials to the Credential Wallet using PowerShell

PS C:\TACG> $Params = @{
 >>   CustomerId = 'uzXXXXXXXXXXX'
 >>   CredentialType = 'UsernamePassword'
 >>   CredentialId = 'platformid'
 >>   UserDomain = 'XXXXXXXXXXXXXXXXXXXX'
 >>   UserName = 'XXXXXXXXXXXXXXXXXXXXXXX'
 >>   UserPassword = 'XXXXXXXXXXXXXXXXXXXXXXXXX'
 >> }

 PS C:\TACG> New-IpsCredentials @Params
 Logging to C:\TACG\Credentials.log Verbose=False
 Interactively authenticating for Citrix customer uzXXXXXXXXXXX.
 Authenticated for Citrix customer uzXXXXXXXXXXX.
 Creating new UsernamePassword credential platformid
 geo EU api url https://api.eu.layering.cloud.com/
 Created credential id platformid for name platformid
 platformid

Part 1: Exporting the Image from on-premises vSphere using Postman

Before we can export the Image we need more information from the vSphere platform: The following parameters must be obtained from vSphere:

vCenterHost
vCenterPort
datacenter
datastore
cluster
network
sourceDisk

The following parameters are also required:

-  Prefix:  name of the export job
-  ResourceLocationID:  ID of the Resource Location of the Image
-  OutputStorageLocation:  
    -  Type (for example SMB)
    -  Credential-ID (for example SMB)
    -  Host-IP (for example 10.10.11.44):  must be reachable by the Connector Appliance in the Resource Location of the Image
    -  SharePath (for example SMB):  must be reachable by the Connector Appliance in the Resource Location of the Image

When all values are set we can call the API to export the Image.

poc-guides_ips-postman-export.png

POST https://api.eu.layering.cloud.com/Images/$export?async=true`

Authorization:      CWSAuth bearer=  {{Bearer-Token-Value}}
                    Citrix-CustomerId: {{Citrix-CustomerID}}

Body (Raw):
{
  "platform": "vSphere",
  "platformCredentialId": "vsXXXXXXXXX",
  "vCenterHost": "(FQDN)",
  "vCenterPort": 443,
  "vCenterSslNoCheckHostname": "true",
  "datacenter": "XXXX",
  "datastore": "XXXXX",
  "cluster": "TACG",
  "network": "VM Network",
  "prefix": "my-cejob",
  "resourceLocationId": "58110e95-XXXX-XXXX-XXXX-XXXXXXXXXX",
  "outputStorageLocation": {
    "type": "SMB",
    "credentialId": "XXXXXXXXX",
    "host": "10.10.11.99",
    "sharePath": "_sw"
  },
  "outputImageFilename": "my-exported-Image",
  "timeoutInSeconds": 3600,
  "sourceDiskName": "ds:///vmfs/volumes/65245805-XXXX-XXXX-XXXX-48210b5c6cd7/TACG-VSP-W11-M/TACG-VSP-W11-M.vmdk" 
}

Response:

{
    "id": "abcXXXX-XXXX-XXXX-XXXX-XXXXXXXXXX",
    "type": "exportImage",
    "overallProgressPercent": 0,
    "isCancellable": true,
    "parameters": [],
    "status": "notStarted",
    "resultLocation": null,
    "additionalInfo": null,
    "warnings": [],
    "error": [],
    "createdAt": "2023-10-31T14:05:15Z",
    "updatedAt": "2023-10-31T14:05:15Z",
    "startedAt": "2023-10-31T14:05:15Z"
}

As the job runs asynchronously, we need another REST-API-call to get the status of the export job using the id parameter from the response:

poc-guides_ips-postman-export-status.png

GET 
https://api.eu.layering.cloud.com/jobs/{job-id}`

Authorization:      CWSAuth bearer= {{Bearer-Token-Value}}
                    Citrix-CustomerId: {{Citrix-CustomerID}}

The Response of the call contains all progress and error information. If the call is successful, we see the exported Image on the referenced SMB share.

Part 1: Exporting the Image from on-premises vSphere using PowerShell

Before we can export the Image we need more information from the vSphere platform: The following parameters must be obtained from vSphere:

vCenterHost
vCenterPort
datacenter
datastore
cluster
network
sourceDisk

The following parameters are also required:

-  Prefix:  name of the export job
-  ResourceLocationID:  ID of the Resource Location of the Image
-  OutputStorageLocation:  
    -  Type (for example SMB)
    -  Credential-ID (for example SMB)
    -  Host-IP (for example 10.10.11.44):  must be reachable by the Connector Appliance in the Resource Location of the Image
    -  SharePath (for example SMB):  must be reachable by the Connector Appliance in the Resource Location of the Image

When all values are set we can call the API to export the Image:

PS C:\TACG> $ExportParams = @{
>>   CustomerId = 'XXXXXXXXXX'
>>   SecureClientId = '71XXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXb'
>>   SecureSecret = 'd-XXXXXXXXXXXXXX=='
>>   SmbHost = '10.10.11.99'
>>   SmbShare = '_SW'
>>   SmbDiskName = 'ipsexp-ps'
>>   SmbDiskFormat = 'VhdDiskFormat'
>>   SmbCwId = 'SMB'
>>   ResourceLocationId = '58XXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXX2'
>>   VsphereCwSecretId = 'vsXXXXXXX'
>>   VsphereHost = '(FQDN)'
>>   VspherePort = '443'
>>   VsphereSslNoCheckHostname = $true
>>   VsphereDataCenter = 'TACG'
>>   VsphereDataStore = 'XXXXXXXXXXXX'
>>   VsphereNetwork = 'VM Network'
>>   VsphereCluster = 'TAXXXXXXXXXX'
>>   SourceDiskName = "ds:///vmfs/volumes/65245805-87e7af41-5aeb-48210b5c6cd7/TACG-VSP-W11-M/TACG-VSP-W11-M.vmdk"
>> }

PS C:\TACG> Start-IpsVsphereExportJob @ExportParams -Verbose | Wait-IpsJob
Logging to C:\TACG\ExportVsphereToSmb.log Verbose=True
Authenticating for Citrix customer XXXXXXXX using API key XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXX.
VERBOSE: apikey
VERBOSE: __AllParameterSets
VERBOSE: Get-XDAuthentication: Enter
VERBOSE: invoking Get-XDAuthenticationEx:
VERBOSE: Get-XDAuthentication: Exit
Authenticated for Citrix customer XXXXXXXX.
Starting export workflow
***** Call Method: ExportImageJob overwrite: False *****
geo EU api url https://api.eu.layering.cloud.com/
VERBOSE: POST with -1-byte payload
VERBOSE: received 326-byte response of content type application/json; charset=utf-8
Image Export started with id 7a4XXXXXXX-XXXX-XXXX-XXXXXXXXXX
Logging to C:\TACG\ExportVsphereToSmb.log Verbose=False
Interactively authenticating for Citrix customer XXXXXXXX.
Authenticated for Citrix customer XXXXXXXXX.
geo EU api url https://api.eu.layering.cloud.com/
Job 7a4XXXXXXX-XXXX-XXXX-XXXXXXXXXX status: inProgress  percent done: 0  parameters @{name=currentStep; value=GenerateSecurityData}
Geo EU api url https://api.eu.layering.cloud.com/
Job 7a4XXXXXXX-XXXX-XXXX-XXXXXXXXXX status: inProgress  percent done: 27  parameters @{name=currentStep; value=CreateCeVm}
geo EU api url https://api.eu.layering.cloud.com/
Job 7a4XXXXXXX-XXXX-XXXX-XXXXXXXXXX status: inProgress  percent done: 67  parameters @{name=currentStep; value=Wait ForExportImage}
geo EU api url https://api.eu.layering.cloud.com/
Job7a4XXXXXXX-XXXX-XXXX-XXXXXXXXXX status: inProgress  percent done: 67  parameters @{name=currentStep; value=Wait ForExportImage}
geo EU api url https://api.eu.layering.cloud.com/
Job 7a4XXXXXXX-XXXX-XXXX-XXXXXXXXXX status: inProgress  percent done: 73  parameters @{name=currentStep; value=GetCeLogs}
geo EU api url https://api.eu.layering.cloud.com/
Job 7a4XXXXXXX-XXXX-XXXX-XXXXXXXXXX status: complete  percent done: 100  parameters
Job 7a4XXXXXXX-XXXX-XXXX-XXXXXXXXXX final status: complete
Job profile @{created compositing engine=0:01:12.666709, prepared to create compositing engine=0:00:01.100751, deleted compositing engine=0:00:01.774440, uploaded export assets to compositing engine=00:00:03.0517790})
Artifacts                       Status   LogFileDir LogFileName
---------                       ------   ---------- -----------
{output, tags, disk, temporary} complete            ExportVsphereToSmb.log

If the PowerShell script completed successfully, we see the exported Image on the referenced SMB share.

Part 1: Exporting the Image from on-premises vSphere using the Windows .NET Application

The first step obtains a Bearer token automatically.

Before we can export the Image we need more information from the vSphere platform: The following parameters must be obtained from vSphere:

vCenterHost
vCenterPort
datacenter
datastore
cluster
network
sourceDisk

The following parameters are also required:

-  Prefix:  name of the export job
-  ResourceLocationID:  ID of the Resource Location of the Image
-  OutputStorageLocation:  
    -  Type (for example SMB)
    -  Credential-ID (for example SMB)
    -  Host-IP (for example 10.10.11.44):  must be reachable by the Connector Appliance in the Resource Location of the Image
    -  SharePath (for example SMB):  must be reachable by the Connector Appliance in the Resource Location of the Image

Parts of the parameters have to be stored in the adjacent JSON-files of the Windows application:

poc-guides_ips-net-export-variables.png

{
  "vCenterHost": "vcenter.the-austrian-citrix-guy.at",
  "datacenter": "TACG",
  "datastore": "datastore1 (1)",
  "cluster": "TACG-CLUSTER",
  "network": "VM Network",
  "prefix": "my-cejob",
  "outputImageFilename": "my-exported-Image",
  "timeoutInSeconds": 36000,
"sourceDiskName": "ds:///vmfs/volumes/65XXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXd7/TACG-VSP-W11-M/TACG-VSP-W11-M.vmdk",
"smb": {
  "type": "SMB",
  "host": "10.10.11.44",
  "sharePath": "_TRANS"
}
}

The Resource Location-ID, the Platform-Credential, and the SMB Share-Credential are loaded from Citrix Cloud and can be selected at the adjacent drop-down boxes.

poc-guides_ips-net-export-dd.png

When all parameters are loaded, the export can be started by clicking the Export Image to SMB share.

After starting the export the application requests a status update at regular intervals as the export process is an asynchronous task. Progress is shown until the export has been completed.

poc-guides_ips-net-export-progress.png

Part 2: Uploading the Image - Prerequisites

The upload stage uploads the Image to the target cloud subscription. This process is a point-to-point transfer using the configuration and credentials supplied.

Note:

IPS provides PowerShell-based support for automating the upload of the disk. Other third-party tools can also support automation for uploading disks, but Citrix cannot directly support them.

In this guide we upload the Image to Microsoft Azure.

The following Azure permissions are necessary for uploading the Image (the Azure Resource group must be set in the Parameters):

Microsoft.Compute/disks/beginGetAccess/action
Microsoft.Compute/disks/endGetAccess/action
Microsoft.Compute/disks/delete
Microsoft.Compute/disks/read
Microsoft.Compute/disks/write
Microsoft.Compute/virtualMachines/delete
Microsoft.Compute/virtualMachines/powerOff/action
Microsoft.Compute/virtualMachines/read
Microsoft.Compute/virtualMachines/write
Microsoft.Network/networkInterfaces/delete
Microsoft.Network/networkInterfaces/join/action
Microsoft.Network/networkInterfaces/read
Microsoft.Network/networkInterfaces/write
Microsoft.Network/networkSecurityGroups/delete
Microsoft.Network/networkSecurityGroups/join/action
Microsoft.Network/networkSecurityGroups/read
Microsoft.Network/networkSecurityGroups/write
Microsoft.Resources/deployments/operationStatuses/read
Microsoft.Resources/deployments/read
Microsoft.Resources/deployments/write
Microsoft.Resources/subscriptions/resourcegroups/read

For current permission requirements look at the product documentation https://docs.citrix.com/en-us/citrix-daas/migrate-workloads.html#microsoft-azure-required-permissions.

Part 2: Uploading the Image from an on-premises SMB share to Azure using PowerShell

Before we can upload the Image, at least the following parameters must be obtained:

Filename            Filename of the disk to be uploaded
ManagedDiskName     Filename of the disk in Azure
SubscriptionID      Subscription-ID of the Azure tenant used
Location            Azure location
ResourceGroup       Azure Resource group
Timeout             Timeout before the script fails

When all values are set we can call the PowerShell script to export the Image. Be aware that the upload takes quite a while - in this example more than 16 hours…

PS C:\TACG> $Params = @{
 >>   Filename = 'C:\_SW\w11exp.vhd'
 >>   ManagedDiskName = 'IPS_W11EXP.vhd'
 >>   SubscriptionID = '58daXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX'
 >>   Location = 'eastus'
 >>   ResourceGroup = 'CTX-HibTest2'
 >>   Timeout = '86400'
 >> }

 PS C:\TACG> Copy-DiskToAzure @Params -Verbose                                                                                                                                                 Uploading                                                                                                                                            
  Uploading VHD                                                                                                                                      [                                                                                   ]                                                                                                                                                             15:06:19 689995776/85899345920   

2023-11-13 11:53:30Z: VHD size for C:\_SW\w11exp.vhd is 85899346432
2023-11-13 11:53:30Z: Creating managed disk 'IPS_W11EXP.vhd' with size 85899346432 bytes in resource group CTX-HibTest2 location eastus
2023-11-13 11:54:05Z: Copying disk 'C:\_SW\w11exp.vhd' to managed disk 'IPS_W11EXP.vhd' (threads=default)
2023-11-13 11:54:05Z: Detecting source type
2023-11-13 11:54:05Z: Analyzing VHD
2023-11-13 11:54:06Z: Uploading VHD
2023-11-14 04:27:56Z: Copied disk to Azure managed disk 'IPS_W11EXP.vhd'

Part 2: Uploading the Image from an on-premises SMB share to Azure using REST-API

Note:

As mentioned, uploading the Image from an on-premises SMB to Azure is not possible using REST-API calls. You have to invoke PowerShell.

Part 2: Uploading the Image from an on-premises SMB share to Azure using REST-API

The Windows .NET application encapsulates REST-API calls and invokes PowerShell scripts. The VHD file must reside on the computer where the .NET application is run.

Before we can upload the Image, at least the following parameters must be obtained:

Filename            Filename of the disk to be uploaded
ManagedDiskName     Filename of the disk in Azure
SubscriptionID      Subscription-ID of the Azure tenant used
Location            Azure location
ResourceGroup       Azure Resource group
Timeout             Timeout before the script fails

These parameters are set in a JSON-file or directly in the application.

poc-guides_ips-net-variables-upload-json.png

When all values are set, the upload can be started by pressing the “Upload VHD...” button.

poc-guides_ips-net-upload-progress.png

The upload is a long-lasting process depending on the size of the Image and the upload speed - in this example, it took over 16 hours to upload. Set the timeout parameter accordingly!

Note:

During the upload, the application does not refresh its state - it happens only after completion or occurrence of an error!

poc-guides_ips-net-upload-success.png

After a successful upload, the next step of the Image Migration workflow can be started.

Part 3: Preparing the Image - Prerequisites

The Prepare stage is a complex set of steps removing the source platform components and injecting the target platform components into the Image. An important prerequisite is a correct App registration in Azure and its IAM settings. The needed permissions can be found in the IPS documentation. Create the credential in the Citrix Credential Wallet using the PowerShell script or REST-API.

Example for creating Azure credentials in the Credential Wallet using a REST-API call:

Correct format:

{
  "id": "azadminjson",
  "name": "My Azure credential SPN",
  "type": "Azure",
  "tenantId": "e85XXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
  "clientId": "fd7XXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
  "clientSecret": "8cD8QXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX_"
}

The XXXXX-marked values are the corresponding values of the App registration in Azure.

poc-guides_ips-postman-prepare-creds.png

Full REST-API-Call to create the Azure credential:

POST https://api.eu.layering.cloud.com/credentials 

Authorization:      CWSAuth bearer=  {{Bearer-Token-Value}}
Citrix-CustomerId: {{Citrix-CustomerID}}
Accept:            application/json
Content-Type:      application/json

Body (Raw):
{
 "id": "azadminjson",
  "name": "My Azure credential SPN",
  "type": "Azure",
  "tenantId": "e85aXXXX-XXXX-XXXX-XXXXXXXXXXXX”,
  "clientId": "fd72XXXX-XXXX-XXXX-XXXXXXXXXXXX",
  "clientSecret": "8cDXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX_"
}

Response if credential was successfully created:

{
    "id": "azadminjson,
    "name": "My Azure credential SPN",
    "type": "Azure",
    "state": "Ok",
    "createdAt": "2023-11-15T18:01:49.8436445Z",
    "updatedAt": "2023-11-15T18:01:49.8436445Z"
}

Part 3: Preparing the Image on Azure using PowerShell

Before we can call the PowerShell script to prepare the Image, at least the following parameters must be obtained:

 CustomerID
 CloudCwSecretId                        Azure credentials stored in the Credential Wallet
 ResourceLocationId                     ID of the Citrix Cloud Resource location
 AzureSubscriptionID                    Azure region where the prepared disk is placed
 TargetResourceGroup                    Resource group where the prepared disk is placed
 AzureVirtualNetworkResourceGroupName   Resource group for placing the Compositing engine
 AzureVirtualNetworkName                Network for placing the Compositing engine
 AzureVirtualNetworkSubnetName          Subnet for placing the Compositing engine
 CloudProvisioningType                  Deployment with MCS or PVS
 DomainUnjoin                           Remove Image from Domain
 InstallMisa                            Install the Machine Identity Service Agent from VDA
 ForceMisa                              Install the latest Machine Identity Service Agent
 CloudDiskName                          Name of the disk to be prepared
 AzureLocation                          Azure region to deploy to
 XdReconfigure:
      ParameterName                     "controllers"
      "ParameterValue                   FQDN of the Cloud Controllers

When all values are set we can call the PowerShell script to prepare the Image:

 PS C:\TACG> $PrepareParams = @{
 >> CustomerId = 'uzyo2lp7eh7j'
 >> CloudProvisioningType = 'Mcs'
 >> CloudCwSecretId = 'azadXXXXXXXXX'
 >> DomainUnjoin = $true
 >> InstallMisa = $false
 >> ForceMisa = $false
 >> CloudDiskName = 'diskfromipsss'
 >> XdReconfigure =  @(
 >>     [pscustomobject]@{
 >>        ParameterName = 'controllers'
 >>        ParameterValue = 'TACG-XXXXXX.hib.the-austrian-citrix-guy.at'
 >>    }
 >>  )
 >> ResourceLocationId = '7691XXXX-XXXX-XXXX-XXXXXXXXXXXX'
 >> AzureSubscriptionID = '58daXXXX-XXXX-XXXX-XXXXXXXXXXXX'
 >> AzureLocation = 'eastus'
 >> TargetResourceGroup = 'CTX-XXXXXXXXXX
 >> AzureVirtualNetworkResourceGroupName = 'CTX-XXXXXXXXXX'
 >> AzureVirtualNetworkName = 'TACG-XXXXXXXXXX-vnet'
 >> AzureVirtualNetworkSubnetName = 'default'
 >> }

 PS C:\TACG> Start-IpsAzurePrepareJob @PrepareParams -Verbose | Wait-IpsJob
 VERBOSE: Initialize default drives.
 VERBOSE: Creating a new drive.
 VERBOSE: Creating a new drive.
 Logging to C:\TACG\PrepareAzure.log Verbose=True
 Interactively authenticating for Citrix customer uzyo2lp7eh7j.
 VERBOSE: __AllParameterSets
 VERBOSE: Get-XDAuthentication: Enter
 VERBOSE: invoking Get-XDAuthenticationEx:
 VERBOSE: Get-XDAuthentication: Exit
 Authenticated for Citrix customer XXXXXXXXXX.
 Starting prepare workflow
 ***** Call Method: PrepareImageJob *****
 geo EU api url https://api.eu.layering.cloud.com/

 VERBOSE: Cmdlet "Find-Package" is exported.
 VERBOSE: Cmdlet "Get-Package" is exported.
 VERBOSE: Cmdlet "Get-PackageProvider" is exported.
 VERBOSE: Cmdlet "Get-PackageSource" is exported.
 VERBOSE: Cmdlet "Install-Package" is exported.
 VERBOSE: Cmdlet "Import-PackageProvider" is exported.
 VERBOSE: Cmdlet "Find-PackageProvider" is exported.
 VERBOSE: Cmdlet "Install-PackageProvider" is exported.
 VERBOSE: Cmdlet "Register-PackageSource" is exported.
 VERBOSE: Cmdlet "Save-Package" is exported.
 VERBOSE: Cmdlet "Set-PackageSource" is exported.
 VERBOSE: Cmdlet "Uninstall-Package" is exported.
 VERBOSE: Cmdlet "Unregister-PackageSource" is exported.
 VERBOSE: Cmdlet "Find-Package" is imported.
 VERBOSE: Cmdlet "Find-PackageProvider" is imported.
 VERBOSE: Cmdlet "Get-Package" is imported.
 VERBOSE: Cmdlet "Get-PackageProvider" is imported.
 VERBOSE: Cmdlet "Get-PackageSource" is imported.
 VERBOSE: Cmdlet "Import-PackageProvider" is imported.
 VERBOSE: Cmdlet "Install-Package" is imported.
 VERBOSE: Cmdlet "Install-PackageProvider" is imported.
 VERBOSE: Cmdlet "Register-PackageSource" is imported.
 VERBOSE: Cmdlet "Save-Package" is imported.
 VERBOSE: Cmdlet "Set-PackageSource" is imported.
 VERBOSE: Cmdlet "Uninstall-Package" is imported.
 VERBOSE: Cmdlet "Unregister-PackageSource" is imported.
 VERBOSE: POST with -1-byte payload
 VERBOSE: received 327-byte response of content type application/json; charset=utf-8

 Image Prepare started with id 3743XXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
 Logging to C:\TACG\PrepareAzure.log Verbose=False
 Interactively authenticating for Citrix customer XXXXXXXXXX.
 Authenticated for Citrix customer XXXXXXXXXX.

 geo EU api url https://api.eu.layering.cloud.com/
 Job 33743XXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX status: inProgress  percent done: 21  parameters @{name=currentStep; value=CreateCeVm}
 geo EU api url https://api.eu.layering.cloud.com/
 Job 3743XXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX status: inProgress  percent done: 21  parameters @{name=currentStep; value=CreateCeVm}
 geo EU api url https://api.eu.layering.cloud.com/
 Job 3743XXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX status: inProgress  percent done: 21  parameters @{name=currentStep; value=CreateCeVm}
 geo EU api url https://api.eu.layering.cloud.com/
 Job 3743XXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX status: inProgress  percent done: 42  parameters @{name=currentStep; value=UploadAssetsToCe}
 geo EU api url https://api.eu.layering.cloud.com/
 Job 3743XXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX status: inProgress  percent done: 42  parameters @{name=currentStep; value=UploadAssetsToCe}
 geo EU api url https://api.eu.layering.cloud.com/
 Job 3743XXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX status: inProgress  percent done: 53  parameters @{name=currentStep; value=WaitForPrepareImage}
 geo EU api url https://api.eu.layering.cloud.com/
 Job 3743XXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX status: inProgress  percent done: 58  parameters @{name=currentStep; value=GetCeLogs}
 geo EU api url https://api.eu.layering.cloud.com/
 Job 3743XXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX status: inProgress  percent done: 68  parameters @{name=currentStep; value=WaitForTargetBoot}
 geo EU api url https://api.eu.layering.cloud.com/
 Job 3743XXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX status: inProgress  percent done: 68  parameters @{name=currentStep; value=WaitForTargetBoot}
 geo EU api url https://api.eu.layering.cloud.com/
 Job 3743XXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX status: inProgress  percent done: 68  parameters @{name=currentStep; value=WaitForTargetBoot}
 geo EU api url https://api.eu.layering.cloud.com/
 Job 3743XXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX status: inProgress  percent done: 89  parameters @{name=currentStep; value=DeleteCeVm}
 geo EU api url https://api.eu.layering.cloud.com/
 Job 3743XXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX status: inProgress  percent done: 89  parameters @{name=currentStep; value=DeleteCeVm}
 geo EU api url https://api.eu.layering.cloud.com/
 Job 3743XXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX status: inProgress  percent done: 89  parameters @{name=currentStep; value=DeleteCeVm}
 geo EU api url https://api.eu.layering.cloud.com/
 Job 3743XXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX status: complete  percent done: 100  parameters
 Job 3743XXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX final status: complete
 Job profile @{deleted compositing engine=0:03:17.912184, reconfigure VDA=00:02:45.0545592, created compositing engine=0:02:56.629924, installed windowsAzureVmAgent=00:00:56.9625675, prepared to create compositing engine=0:00:08.829692, uploaded prepare assets to compositing engine=00:00:58.2018150})

 Artifacts                 Status   LogFileDir LogFileName
 ---------                 ------   ---------- -----------
 {output, tags, temporary} complete            PrepareAzure.log

The preparation of the Image has been completed successfully.

Part 3: Preparing the Image on Azure using REST-API

An important prerequisite is a correct App registration in Azure and its IAM settings and a correct Azure-based credential. The needed permissions can be found in the IPS documentation.

Create the credential in the Citrix Credential Wallet using the PowerShell script or REST-API.

Before we can call the REST-API to prepare the Image, at least the following parameters must be obtained:

 platform                           Azure
 platformCredentialId               Azure credentials stored in the Credential Wallet
 resourceLocationId                 ID of the Citrix Cloud Resource location
 SubscriptionID                     Azure region where the prepared disk is placed
 targetDiskResourceGroupName        Resource group where the prepared disk is placed
 virtualNetworkResourceGroupName    Resource group for placing the Compositing engine
 virtualNetworkName                 Network for placing the Compositing engine
 virtualNetworkSubnetName           Subnet for placing the Compositing engine
 provisioningType                   Deployment with MCS or PVS
 domainUnjoin                       Remove Image from Domain
 installMisa                        Install the Machine Identity Service Agent from VDA
 forceMisa                          Install the latest Machine Identity Service Agent
 installUpl                         Install User Personal Layer
 defrag                             Run DEFRAG
 chkdsk                             Run CHKDSK
 targetDiskName                     Name of the disk to be prepared
 outputDiskName                     Name of the prepared disk 
 XdReconfigure:
      ParameterName                 "controllers"
      ParameterValue                FQDN of the Cloud Controllers

poc-guides_ips-postman-prepare.png

JSON-Call to prepare the Image:

POST https://api.eu.layering.cloud.com/Images/$prepare?async=true `

Authorization:      CWSAuth bearer=  {{Bearer-Token-Value}}
Citrix-CustomerId: {{Citrix-CustomerID}}
Accept:            application/json
Content-Type:      application/json

Body (Raw):
{
  "platform": "Azure",
  "platformCredentialId": "azadmXXXXXXX",
  "resourceLocationId": "7691XXXX-XXXX-XXXX-XXXX-XXXXXXXXXX",
  "SubscriptionID": "58daXXXX-XXXX-XXXX-XXXX-XXXXXXXXXX",
  "azureRegion": "eastus",
  "targetDiskResourceGroupName": "CTX-XXXXXXXXX",
  "virtualNetworkResourceGroupName": "CTX-XXXXXXXXX",
  "virtualNetworkName": "TACG-XXXXXXXXXXXXX",
  "virtualNetworkSubnetName": "default",
  "provisioningType": "Mcs",
  "domainUnjoin": true,
  "installMisa": false,
  "forceMisa": false,
  "installUpl": false,
  "defrag": false,
  "chkdsk": false,
  "ceVmSku": "Standard_D2s_v5",
  "targetDiskName": "diskromXXXXXXXX",
  "outputDiskName": "procdiskXXXXXXXXX",
  "XdReconfigure": [{
      "ParameterName":"controllers",
      "ParameterValue":"TACGXXXXXXXX.hib.the-austrian-citrix-guy.at"
    }
  ]
}

Response:

{
    "id": "6267XXXX-XXXX-XXXX-XXXX-XXXXXXXXXX",
    "type": "prepareImage",
    "overallProgressPercent": 0,
    "isCancellable": true,
    "parameters": [],
    "status": "notStarted",
    "resultLocation": null,
    "additionalInfo": null,
    "warnings": [],
    "error": [],
    "createdAt": "2023-11-15T19:07:09Z",
    "updatedAt": "2023-11-15T19:07:09Z",
    "startedAt": "2023-11-15T19:07:09Z"
}

As the job runs asynchronously, we need another REST-API-call to get the status of the export job using the id parameter from the response:

Example: 42% progress:

poc-guides_ips-postman-prepare-status.png

Example: 100% progress, preparation successful:

poc-guides_ips-postman-prepare-success.png

JSON-Call to get the status of the preparation job:

GET 
https://api.eu.layering.cloud.com/jobs/6267XXXX-XXXX-XXXX-XXXX-XXXXXXXXX  

Authorization:      CWSAuth bearer=  {{Bearer-Token-Value}}
Citrix-CustomerId: {{Citrix-CustomerID}}
Accept:            application/json
Content-Type:      application/json

Body (Raw):
{
}

Response - this example shows 100% progress and successful preparation

{
    "id": "6267XXXX-XXXX-XXXX-XXXX-XXXXXXXXX  ",
    "type": "prepareImage",
    "overallProgressPercent": 100,
    "isCancellable": true,
    "parameters": [],
    "status": "complete",
    "resultLocation": null,
    "additionalInfo": {
        "artifacts": {
            "tags": {
                "ctx-job-id": "6267XXXX-XXXX-XXXX-XXXX-XXXXXXXXXX",
                "component": "compositing"
            },
            "output": [
                {
                    "description": "Managed disk containing the prepared Image",
                    "name": "procdiskfromipsss",
                    "resourceGroup": "CTX-XXXXXXX"
                }
            ],
            "temporary": [
                {
                    "description": "Temporary resource group",
                    "name": "ctx-ce-50d0e27d"
                },
                {
                    "description": "Temporary Compositing Engine OS disk",
                    "name": "ce-50d0e27d-os-disk",
                    "resourceGroup": "ctx-ce-50d0e27d"
                },
                {
                    "description": "Temporary Compositing Engine NIC",
                    "name": "ce-50d0e27d-nic",
                    "resourceGroup": "ctx-ce-50d0e27d"
                },
                {
                    "description": "Temporary Compositing Engine network security group",
                    "name": "ce-50d0e27d-nic-nsg",
                    "resourceGroup": "ctx-ce-50d0e27d"
                },
                {
                    "description": "Temporary Compositing Engine VM",
                    "name": "ce-50d0e27d",
                    "resourceGroup": "ctx-ce-50d0e27d",
                    "encryptionAtHost": false
                }
            ]
        },
        "profile": {
            "prepared to create compositing engine": "0:00:09.071793",
            "created compositing engine": "0:02:58.483518",
            "uploaded prepare assets to compositing engine": "00:00:54.2715950",
            "installed windowsAzureVmAgent": "00:01:02.6609307",
            "reconfigure VDA": "00:03:54.8636235",
            "deleted compositing engine": "0:03:18.144080"
        },
        "warnings": [],
        "errors": [],
        "telemetry": {
            "diskSize": "85899345920",
            "freeSpace": "1048576",
            "windowsVersion": "@{CurrentVersion=@{value = 6.3, type = REG_SZ}; UBR=@{value = 0x7c8, type = REG_DWORD}; BuildLab=@{value = 22621.ni_release.220506-1250, type = REG_SZ}; RegisteredOwner=@{value = admin, type = REG_SZ}; CurrentBuild=@{value = 22621, type = REG_SZ}; SystemRoot=@{value = C:\\Windows, type = REG_SZ}; DigitalProductId4=@{value = F804000004000000300033003600310032002D00300033003300310031002D003000300030002D003000300030003000300031002D00300033002D0031003000330031002D00320032003600320031002E0030003000300030002D003200380033003200300....000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000, type = REG_BINARY}; ProductId=@{value = 00331-10000-00001-AA757, type = REG_SZ}; DigitalProductId=@{value = A40000000300000030303333312D31303030302D30303030312D414137353700EF0C00005B54485D5831392D3938373935000000EF0C10000000343DC5394EBD6E2F090000000000D51D25653D149BCE030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000050F96851, type = REG_BINARY}; BuildBranch=@{value = ni_release, type = REG_SZ}; SoftwareType=@{value = System, type = REG_SZ}; CurrentType=@{value = Multiprocessor Free, type = REG_SZ}; DisplayVersion=@{value = 22H2, type = REG_SZ}; InstallDate=@{value = 0x65251dd7, type = REG_DWORD}; InstallTime=@{value = 0x1d9fb5edf5aef50, type = REG_QWORD}; InstallationType=@{value = Client, type = REG_SZ}; BuildLabEx=@{value = 22621.1.amd64fre.ni_release.220506-1250, type = REG_SZ}; ProductName=@{value = Windows 10 Pro, type = REG_SZ}; CompositionEditionID=@{value = Enterprise, type = REG_SZ}; CurrentBuildNumber=@{value = 22621, type = REG_SZ}; ReleaseId=@{value = 2009, type = REG_SZ}; PathName=@{value = C:\\Windows, type = REG_SZ}; EditionID=@{value = Professional, type = REG_SZ}; BuildGUID=@{value = ffffffff-ffff-ffff-ffff-ffffffffffff, type = REG_SZ}; CurrentMajorVersionNumber=@{value = 0xa, type = REG_DWORD}; BaseBuildRevisionNumber=@{value = 0x1, type = REG_DWORD}; PendingInstall=@{value = 0x0, type = REG_DWORD}; CurrentMinorVersionNumber=@{value = 0x0, type = REG_DWORD}}",
            "vdaVersions": "@{AppProtectionVC=@{value = 23.8.0.2, type = REG_SZ}; Citrix HDX Audio x64=@{value = 7.39.0.33, type = REG_SZ}; WmiSdk=@{value = 7.39.0.4, type = REG_SZ}; Citrix HDX Graphics x64=@{value = 7.39.0.36, type = REG_SZ}; WMI-Maschinenverwaltungsprovider=@{value = 7.39.0.4, type = REG_SZ}; (Default)=@{value = 7.39.0.0, type = REG_SZ}; Citrix Start Menu Disconnect Button=@{value = 7.39.0.41, type = REG_SZ}; Citrix Identity Assertion VDA Plugin=@{value = 10.15.0.4, type = REG_SZ}; Citrix Director VDA Plugin=@{value = 7.39.0.4, type = REG_SZ}; Citrix Überwachungsdienst-VDA-Plug-In=@{value = 7.39.0.10, type = REG_SZ}; Citrix WMI Proxy Plugin=@{value = 7.39.0.4, type = REG_SZ}; Citrix Browser Content Redirection=@{value = 15.45.0.12, type = REG_SZ}; Citrix Gruppenrichtlinie - clientseitige Erweiterung 7.39.0.13=@{value = 7.39.0.13, type = REG_SZ}; Citrix HDX Devices x64=@{value = 7.39.0.31, type = REG_SZ}; Machine Identity Service Agent=@{value = 7.39.0.4, type = REG_SZ}; Citrix HDX App Experience x64=@{value = 7.39.0.41, type = REG_SZ}; Citrix CDF Capture Service - x64=@{value = 7.39.0.4, type = REG_SZ}; Citrix HDX Printing x64=@{value = 7.39.0.26, type = REG_SZ}; UpmVDAPlugin=@{value = 23.8.0.7, type = REG_SZ}; Citrix HDX WS x64=@{value = 15.45.0.12, type = REG_SZ}; Citrix HDX IcaManagement x64=@{value = 7.39.0.47, type = REG_SZ}; Citrix Diagnostics Facility=@{value = 7.2.1.6, type = REG_SZ}; Citrix Telemetry Service - x64=@{value = 3.24.0.1, type = REG_SZ}; Citrix Universeller Druckclient=@{value = 7.39.0.26, type = REG_SZ}; Citrix Virtual Desktop Agent - x64=@{value = 7.39.0.11, type = REG_SZ}}",
            "partitionStyle": "GPT",
            "bootMode": "UEFI"
        }
    },
    "warnings": [],
    "error": [],
    "createdAt": "2023-11-15T19:07:09Z",
    "updatedAt": "2023-11-15T19:37:29Z",
    "startedAt": "2023-11-15T19:07:09Z",
    "endedAt": "2023-11-15T19:37:29Z"
}

The preparation of the Image has been completed successfully.

Part 3: Preparing the Image on Azure using the Windows .NET Application

The Windows .NET application encapsulates REST-API calls to prepare the Image on Azure. An important prerequisite is a correct App registration in Azure and its IAM settings and a correct Azure-based credential.

Before the application can prepare the Image, the following parameters must be obtained:

 platform                           Azure
 platformCredentialId               Azure credentials stored in the Credential Wallet
 resourceLocationId                 ID of the Citrix Cloud Resource location
 SubscriptionID                     Azure region where the prepared disk is placed
 targetDiskResourceGroupName        Resource group where the prepared disk is placed
 virtualNetworkResourceGroupName    Resource group for placing the Compositing engine
 virtualNetworkName                 Network for placing the Compositing engine
 virtualNetworkSubnetName           Subnet for placing the Compositing engine
 provisioningType                   Deployment with MCS or PVS
 domainUnjoin                       Remove Image from Domain
 installMisa                        Install the Machine Identity Service Agent from VDA
 forceMisa                          Install the latest Machine Identity Service Agent
 installUpl                         Install User Personal Layer
 defrag                             Run DEFRAG
 chkdsk                             Run CHKDSK
 targetDiskName                     Name of the disk to be prepared
 outputDiskName                     Name of the prepared disk 
 XdReconfigure:
      ParameterName                 "controllers"
      ParameterValue                FQDN of the Cloud Controllers

These parameters are set in a JSON-file.

Note:

The parameter overrideUploadedDiskName determines the preparation of the correct disk: true uses the disk name set in the adjacent JSON file, false uses the disk name uploaded before.

poc-guides_ips-net-prepare-variables.png

When all parameters are set, the preparation of the uploaded Image can be started by pressing the “Prepare VHD...” button. The preparation process lasted in this example for about 35 minutes…

poc-guides_ips-net-prepare.png

The application refreshes every 30 seconds the job status by calling a REST-API to determine the job status.

poc-guides_ips-net-prepare-success.png

The preparation of the Image has been completed successfully.

Part 4: Publishing the Image - Prerequisites

In the Publish phase, the prepared Image is published and ready to be streamed with Citrix Provisioning Server (PVS). The Image Portability Service provides a REST interface and PowerShell scripts to publish the migrated disk directly into the PVS vDisk store.

Note:

If you are using Machine Creation Services (MCS) to provision your machine catalog, you do not need to run the “Publish” stage. You can use the prepared Image from Part 3 to create the Machine Catalog.

An example of creating a Machine Catalog using PowerShell can be found on the Tech Zone article https://docs.citrix.com/en-us/tech-zone/build/deployment-guides/citrix-azure-hibernation-posh#creating-a-hibernation-capable-machine-catalog-using-powershell.

An example of creating a Machine Catalog using REST-API calls can be found on the Tech Zone article https://docs.citrix.com/en-us/tech-zone/build/deployment-guides/citrix-azure-hibernation-api#creating-a-hibernation-capable-machine-catalog-using-rest-api.

You can use these guides as a reference for further automation of creating machines using MCS.

Part 4: Publishing the Image on Azure to an SMB share for PVS using PowerShell

An important prerequisite is a correct App registration in Azure and its IAM settings.

Before we can call the PowerShell script to publish the Image, at least the following parameters must be obtained:

CustomerID                                     Citrix Customer-ID
SecureClientID                                 ID of the API client        
SecureSecret                                   Secret of the API client 
SmbHost                                        Hostname of SMB server where the exported disk will be stored
SmbShare                                       SMB server share name
SmbPath                                        Share path to the disk
SmbDiskName                                    Name of disk with no extension
SmbDiskFormat                                  Type of disk
SmbCwId                                        SMB Credential Wallet ID
ResourceLocationID                             ID of the Resource location      
AzureSubscriptionID                            ID of the Azure subscription  
CloudCWSecretID                                ID of the Cloud credential in the Credential Wallet
AzureLocation                                  Azure location
TargetResourceGroup                            Resource group for placing the Compositing engine  
AzureVirtualNetworkResourceGroupName           Resource group of the Network for placing the Compositing engine                   
AzureVirtualNetworkName                        Network for placing the Compositing engine    
AzureVirtualNetworkSubnetName                  Subnet for placing the Compositing engine     
CloudDiskName                                  Name of the disk to be transferred to the SMB share
AzureVmResourceGroup                           Resource group where the VM will be placed
Timeout                                        Timespan before the process times out
LogFileName                                    Name of the log file   

When all values are set we can call the PowerShell script to publish the Image.

PS C:\TACG> $PublishParams = @{
 >>   CustomerId = "uzXXXXXXXXXXXX"
 >>   SecureClientId = "26ecXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
 >>   SecureSecret = "vhOXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
 >>   SmbHost = "10.0.0.4"
 >>   SmbShare = "_PVS"
 >>   SmbDiskName = "ReadyForPVSPS"
 >>   SmbDiskFormat = "VhdDiskFormat"
 >>   SmbCwId = "azhibsmb"
 >>   ResourceLocationId = "7691XXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
 >>   AzureSubscriptionId = "58daXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
 >>   CloudCwSecretId = "azadminjson"
 >>   AzureLocation = "eastus"
 >>   TargetResourceGroup = "CTX-Hibtest2"
 >>   AzureVirtualNetworkResourceGroupName = "CTX-Hibtest2"
 >>   AzureVirtualNetworkName = "TACG-HibTest-DCCC-vnet"
 >>   AzureVirtualNetworkSubnetName = "default"
 >>   CloudDiskName = "procdiskfromipsss"
 >>   AzureVmResourceGroup = "CTX-Hibtest2"
 >>   Timeout = "36000"
 >>   LogFileName = "AzurePublish.log"
 >> }

 PS C:\TACG> Start-IpsAzurePublishJob @PublishParams -Verbose | Wait-IpsJob
 VERBOSE: Initialize default drives.
 VERBOSE: Creating a new drive.
 VERBOSE: Creating a new drive.

 Logging to C:\TACG\AzurePublish.log Verbose=True
 Authenticating for Citrix customer uzXXXXXXXXXXXXXX using API key 26ecXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX.
 VERBOSE: apikey
 VERBOSE: __AllParameterSets
 VERBOSE: Get-XDAuthentication: Enter
 VERBOSE: invoking Get-XDAuthenticationEx:
 VERBOSE: Get-XDAuthentication: Exit
 Authenticated for Citrix customer uzyXXXXXXXXXX.
 Starting export workflow

 ***** Call Method: ExportImageJob overwrite: False *****
 geo EU api url https://api.eu.layering.cloud.com/
 VERBOSE: Cmdlet "Find-Package" is exported.
 VERBOSE: Cmdlet "Get-Package" is exported.
 VERBOSE: Cmdlet "Get-PackageProvider" is exported.
 VERBOSE: Cmdlet "Get-PackageSource" is exported.
 VERBOSE: Cmdlet "Install-Package" is exported.
 VERBOSE: Cmdlet "Import-PackageProvider" is exported.
 VERBOSE: Cmdlet "Find-PackageProvider" is exported.
 VERBOSE: Cmdlet "Install-PackageProvider" is exported.
 VERBOSE: Cmdlet "Register-PackageSource" is exported.
 VERBOSE: Cmdlet "Save-Package" is exported.
 VERBOSE: Cmdlet "Set-PackageSource" is exported.
 VERBOSE: Cmdlet "Uninstall-Package" is exported.
 VERBOSE: Cmdlet "Unregister-PackageSource" is exported.
 VERBOSE: Cmdlet "Find-Package" is imported.
 VERBOSE: Cmdlet "Find-PackageProvider" is imported.
 VERBOSE: Cmdlet "Get-Package" is imported.
 VERBOSE: Cmdlet "Get-PackageProvider" is imported.
 VERBOSE: Cmdlet "Get-PackageSource" is imported.
 VERBOSE: Cmdlet "Import-PackageProvider" is imported.
 VERBOSE: Cmdlet "Install-Package" is imported.
 VERBOSE: Cmdlet "Install-PackageProvider" is imported.
 VERBOSE: Cmdlet "Register-PackageSource" is imported.
 VERBOSE: Cmdlet "Save-Package" is imported.
 VERBOSE: Cmdlet "Set-PackageSource" is imported.
 VERBOSE: Cmdlet "Uninstall-Package" is imported.
 VERBOSE: Cmdlet "Unregister-PackageSource" is imported.
 VERBOSE: POST with -1-byte payload
 VERBOSE: received 326-byte response of content type application/json; charset=utf-8
 Image Export started with id 467XXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
 Logging to C:\TACG\AzurePublish.log Verbose=False
 Interactively authenticating for Citrix customer uzyXXXXXXXXXX.
 Authenticated for Citrix customer uzyXXXXXXXXXX.
 geo EU api url https://api.eu.layering.cloud.com/
 Job 467XXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX status: inProgress  percent done: 20  parameters @{name=currentStep; value=PrepareForCreateCeVm}
 geo EU api url https://api.eu.layering.cloud.com/
 Job 467XXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX status: inProgress  percent done: 20  parameters @{name=currentStep; value=PrepareForCreateCeVm}
 geo EU api url https://api.eu.layering.cloud.com/
 Job 467XXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX status: inProgress  percent done: 27  parameters @{name=currentStep; value=CreateCeVm}
 geo EU api url https://api.eu.layering.cloud.com/
 Job 467XXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX status: inProgress  percent done: 27  parameters @{name=currentStep; value=CreateCeVm}
 geo EU api url https://api.eu.layering.cloud.com/
 Job 467XXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX status: inProgress  percent done: 27  parameters @{name=currentStep; value=CreateCeVm}
 geo EU api url https://api.eu.layering.cloud.com/
 Job 467XXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX status: inProgress  percent done: 67  parameters @{name=currentStep; value=WaitForExportImage}
 geo EU api url https://api.eu.layering.cloud.com/
 Job 467XXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX status: inProgress  percent done: 67  parameters @{name=currentStep; value=WaitForExportImage}
 geo EU api url https://api.eu.layering.cloud.com/
 Job 467XXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX status: inProgress  percent done: 67  parameters @{name=currentStep; value=WaitForExportImage}
 geo EU api url https://api.eu.layering.cloud.com/
 Job 467XXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX status: inProgress  percent done: 87  parameters @{name=currentStep; value=DeleteCeVm}
 geo EU api url https://api.eu.layering.cloud.com/
 Job 467XXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX status: inProgress  percent done: 87  parameters @{name=currentStep; value=DeleteCeVm}
 geo EU api url https://api.eu.layering.cloud.com/
 Job 467XXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX status: complete  percent done: 100  parameters
 Job 467XXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXXfinal status: complete
 Job profile @{created compositing engine=0:03:01.698686, prepared to create compositing engine=0:00:07.768624, deleted compositing engine=0:01:35.971565, uploaded export assets to compositing eng
 ine=00:00:03.2609860})

 Artifacts                       Status   LogFileDir LogFileName
 ---------                       ------   ---------- -----------
 {output, tags, disk, temporary} complete            AzurePublish.log

 PS C:\TACG>

The publishing of the Image has been completed successfully. All steps of the IPS workflow have completed successfully.

The next step is deploying the Image using Citrix Provisioning Services (PVS). More information about Citrix Provisioning Services (PVS) and how to deploy Images/machines using PVS can be found here: https://docs.citrix.com/en-us/tech-zone/build/deployment-guides/citrix-azure-hibernation-api#creating-a-hibernation-capable-machine-catalog-using-rest-api and https://docs.citrix.com/en-us/tech-zone/learn/tech-briefs/citrix-provisioning.

Part 4: Publishing the Image on Azure to an SMB share for PVS using REST-API

An important prerequisite is a correct App registration in Azure and its IAM settings.

Before we can call the PowerShell script to publish the Image, at least the following parameters must be obtained:

platform                               Azure
platformCredentialId                   Azure credentials stored in the Credential Wallet
resourceLocationId                     ID of the Citrix Cloud Resource location
SubscriptionID                         Azure region where the prepared disk is placed
targetDiskResourceGroupName            Resource group where the prepared disk is placed
virtualNetworkResourceGroupName        Resource group for placing the Compositing engine
virtualNetworkName                     Network for placing the Compositing engine
virtualNetworkSubnetName               Subnet for placing the Compositing engine
targetDiskName                         Name of the disk to be prepared
outputImageFilename                    Name of the published disk 
resourceGroup                          Resource group where the process will run 
timeoutInSeconds                       Timespan before the process will time out
outputDiskName: {                      Name of the prepared disk 
         type                          "SMB” as we publish to a share 
         credentialID                  Credential Wallet-ID of the SMB credential 
         host                          FQDN/IP of the server hosting the SMB share
         sharePath                     Name of the share 
 }

When all values are set we can call the REST-API script to publish the Image.

JSON-Call to start the publishing job:

poc-guides_ips-postman-publish.png

POST https://api.eu.layering.cloud.com/Images/$publish?async=true`

Authorization:      CWSAuth bearer=  {{Bearer-Token-Value}}
Citrix-CustomerId: {{Citrix-CustomerID}}
Accept:            application/json
Content-Type:      application/json

Body (Raw):
  {
  "platform": "Azure",
  "platformCredentialId": "azadminjson",
  "subscriptionId": "58dXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
  "resourceGroup": "CTX-Hibtest2",
  "virtualNetworkResourceGroupName": "CTX-Hibtest2",
  "virtualNetworkName": "TACG-HibTest-DCCC-vnet",
  "virtualNetworkSubnetName": "default",
  "timeoutInSeconds":86400,
  "targetDiskResourceGroupName": "CTX-Hibtest2",
  "targetDiskName": "procdiskfromipsss",
  "resourceLocationId": "769XXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
  "outputStorageLocation": {
    "type": "SMB",
    "credentialId": "azhibsmb",
    "host": "10.0.0.4",
    "sharePath": "_PVS"
  },
  "outputImageFilename": "ReadyForPVS"
}

Response:

{
    "id": "d44XXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
    "type": "exportImage",
    "overallProgressPercent": 0,
    "isCancellable": true,
    "parameters": [],
    "status": "notStarted",
    "resultLocation": null,
    "additionalInfo": null,
    "warnings": [],
    "error": [],
    "createdAt": "2023-11-23T13:31:48Z",
    "updatedAt": "2023-11-23T13:31:48Z",
    "startedAt": "2023-11-23T13:31:48Z"
}

As the job runs asynchronously, we need another REST-API-call to get the status of the export job using the id parameter from the response:

Example: 100% progress, publishing successful:

poc-guides_ips-postman-publish-success.png

JSON-Call to get the status of the publishing job:

GET https://api.eu.layering.cloud.com/jobs/6267XXXX-XXXX-XXXX-XXXX-XXXXXXXXX  

Authorization:      CWSAuth bearer=  {{Bearer-Token-Value}}
Citrix-CustomerId: {{Citrix-CustomerID}}
Accept:            application/json
Content-Type:      application/json

Body (Raw):
{
}

Response - this example shows 100% progress and successful publishing:

{
    "id": "d443XXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
    "type": "exportImage",
    "overallProgressPercent": 100,
    "isCancellable": true,
    "parameters": [],
    "status": "complete",
    "resultLocation": null,
    "additionalInfo": {
        "artifacts": {
            "tags": {
                "ctx-job-id": "d443XXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
                "component": "compositing"
            },
            "output": [
                {
                    "description": "Published disk",
                    "path": "//10.0.0.4/_PVS/ReadyForPVS.vhd"
                }
            ],
            "temporary": [
                {
                    "description": "Temporary Compositing Engine OS disk",
                    "name": "ce-f32672a0-os-disk",
                    "resourceGroup": "CTX-Hibtest2"
                },
                {
                    "description": "Temporary Compositing Engine NIC",
                    "name": "ce-f32672a0-nic",
                    "resourceGroup": "CTX-Hibtest2"
                },
                {
                    "description": "Temporary Compositing Engine network security group",
                    "name": "ce-f32672a0-nic-nsg",
                    "resourceGroup": "CTX-Hibtest2"
                },
                {
                    "description": "Temporary Compositing Engine VM",
                    "name": "ce-f32672a0",
                    "resourceGroup": "CTX-Hibtest2",
                    "encryptionAtHost": false
                },
                {
                    "description": "Temporary copy of the target disk",
                    "name": "procdiskfromipsss-f5276f68",
                    "resourceGroup": "CTX-Hibtest2"
                }
            ],
            "disk": {
                "diskPath": "\\\\10.0.0.4\\_PVS\\ReadyForPVS.vhd",
                "md5Hash": "f96519ee184ba47ba33f03d09384188b"
            }
        },
        "profile": {
            "prepared to create compositing engine": "0:00:07.449354",
            "created compositing engine": "0:03:01.894559",
            "uploaded export assets to compositing engine": "00:00:04.7360280",
            "deleted compositing engine": "0:01:40.349551"
        },
        "warnings": [],
        "errors": []
    },
    "warnings": [],
    "error": [],
    "createdAt": "2023-11-23T13:31:48Z",
    "updatedAt": "2023-11-23T14:39:54Z",
    "startedAt": "2023-11-23T13:31:48Z",
    "endedAt": "2023-11-23T14:39:54Z"
}

The publishing of the Image has been completed successfully. All steps of the IPS workflow have completed successfully. The next step is deploying the Image using Citrix Provisioning Services (PVS). More information about Citrix Provisioning Services (PVS) and how to deploy Images/machines using PVS can be found here: https://docs.citrix.com/en-us/tech-zone/build/deployment-guides/citrix-azure-hibernation-api#creating-a-hibernation-capable-machine-catalog-using-rest-api and https://docs.citrix.com/en-us/tech-zone/learn/tech-briefs/citrix-provisioning.

Part 4: Publishing the Image on Azure to an SMB share for PVS using the .NET application

The Windows .NET application encapsulates REST-API calls to prepare the Image on Azure.

An important prerequisite is a correct App registration in Azure and its IAM settings.

Before we can use the application to publish the Image, at least the following parameters must be obtained:

platform                               Azure
platformCredentialId                   Azure credentials stored in the Credential Wallet
resourceLocationId                     ID of the Citrix Cloud Resource location
SubscriptionID                         Azure region where the prepared disk is placed
targetDiskResourceGroupName            Resource group where the prepared disk is placed
virtualNetworkResourceGroupName        Resource group for placing the Compositing engine
virtualNetworkName                     Network for placing the Compositing engine
virtualNetworkSubnetName               Subnet for placing the Compositing engine
targetDiskName                         Name of the disk to be prepared
timeoutInSeconds                       Timespan before the process will time out
SmbHost                                FQDN/IP of the server hosting the SMB share
SmbShare                               Name of the SMB share
SmbDiskName                            Name of the published disk on the SMB share
SmbDiskFormat                          Disk format

These parameters are set in a JSON-file:

poc-guides_ips-net-publish-variables.png

When all parameters are set, the publishing of the uploaded Image can be started by pressing the “Publish VHD...” button. The publishing process lasted in this example for about 65 minutes…

poc-guides_ips-net-publish.png

The publishing of the Image has been completed successfully.

poc-guides_ips-net-publish-success.png

The next step is deploying the Image using Citrix Provisioning Services (PVS). More information about Citrix Provisioning Services (PVS) and how to deploy Images/machines using PVS can be found here: https://docs.citrix.com/en-us/tech-zone/build/deployment-guides/citrix-azure-hibernation-api#creating-a-hibernation-capable-machine-catalog-using-rest-api and https://docs.citrix.com/en-us/tech-zone/learn/tech-briefs/citrix-provisioning.

Appendix

Code Snippets

Important Note:

These code snippets show examples how to implement the API calls and PowerShell scripts into an application.

No warranty, no liability, and no support of any kind are given for the application.

Example of Objects

Public Class CCAPI_Token
    Public Property CustomerID As String
    Public Property ClientID As String
    Public Property ClientSecret As String
    Public Property GrantType As String
End Class
Public Class CCAPI_ExportParameters 'Nested object
    Public Property platform As String
    Public Property platformCredentialId As String
    Public Property vCenterHost As String
    Public Property vCenterPort As Integer
    Public Property vCenterSslNoCheckHostname As String
    Public Property datacenter As String
    Public Property datastore As String
    Public Property cluster As String
    Public Property network As String
    Public Property prefix As String
    Public Property resourceLocationId As String
    Public Property outputStorageLocation As CCAPI_OutputStorageLocation
    Public Property outputImageFilename As String
    Public Property timeoutInSeconds As Integer
    Public Property sourceDiskName As String
End Class
Public Class CCAPI_OutputStorageLocation
      Public Property type As String
      Public Property credentialId As String
      Public Property host As String
      Public Property sharePath As String
End Class

Example of JSON-based variables

CCAPI-Token.json -> read into Class CCAPI_Token at startup:

{
  "CustomerID": "uzXXXXXXXXXXXXX",
  "ClientID": "5075XXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
  "ClientSecret": "8MXXXXXXXXXXXXXXXXXXXXX==",
  "GrantType": "client_credentials"
}

Obtain a Bearer token from Citrix Cloud

Private Sub FetchBearerToken(ByVal obj_CCAPI_Token As CCAPI_Token)
     'Create POST request to Citrix Cloud-API to retrieve BearerToken
     Dim rest As New Chilkat.Rest

     ' Connect to the CCAPI server.
     Dim bTls As Boolean = True
     Dim port As Integer = 443
     Dim bAutoReconnect As Boolean = True
     Dim success As Boolean = rest.Connect("api-eu.cloud.com", port, bTls, bAutoReconnect)
     If (success <> True) Then
         Debug.WriteLine(rest.LastErrorText)
         Exit Sub
     End If
     success = rest.AddQueryParam("grant_type", obj_CCAPI_Token.GrantType)
     success = rest.AddQueryParam("client_id", obj_CCAPI_Token.ClientID)
     success = rest.AddQueryParam("client_secret", obj_CCAPI_Token.ClientSecret)
     Dim APIPostCallPath As String = "/cctrustoauth2/" & obj_CCAPI_Token.CustomerID & "/tokens/clients"
     Dim s_Response As String = Nothing
     obj_BearerToken = New CCAPI_BearerToken

     Try
         s_Response = rest.FullRequestFormUrlEncoded("POST", APIPostCallPath)
         obj_BearerToken = JsonConvert.DeserializeObject(Of CCAPI_BearerToken)(s_Response, JSONSettings)

     Catch exc As Exception
         Console.WriteLine(exc.ToString)
     End Try

     If s_Response <> "" Then
         Dim json As JObject = JObject.Parse(s_Response)
         obj_BearerToken.TokenType = json.SelectToken("token_type")
         obj_BearerToken.Expiry = json.SelectToken("expires_in")
         obj_BearerToken.AccessToken = json.SelectToken("access_token")

         txtParams(0) = txt_status
         txtParams(1) = "Successfully obtained Bearer-Token..." & vbCrLf
         Me.Invoke(New WriteTextDelegate(AddressOf WriteText), txtParams)

         txt_bearertoken.Text = obj_BearerToken.AccessToken.ToString

         txtParams(0) = txt_status
         txtParams(1) = "-------------------------------" & vbCrLf & vbCrLf
         Me.Invoke(New WriteTextDelegate(AddressOf WriteText), txtParams)
     End If
 End Sub

Set all Export parameters and export Image from vSphere

Private Sub SetAllExportParameters(ByVal obj_CCAPI_Token As CCAPI_Token)
    s_APIBody = New Chilkat.StringBuilder
    s_APIBody.AppendLine("{", True)
    obj_AllExportParameters = New CCAPI_ExportParameters
    obj_AllExportParameters.outputStorageLocation = New CCAPI_OutputStorageLocation
    obj_AllExportParameters.vCenterPort = 443
    s_APIBody.AppendLine((Chr(34) & "vCenterPort" & Chr(34) & ": " & Chr(34) & obj_AllExportParameters.vCenterPort & Chr(34) & ","), True)
    obj_AllExportParameters.vCenterHost = obj_Export.vCenterHost
    s_APIBody.AppendLine((Chr(34) & "vCenterHost" & Chr(34) & ": " & Chr(34) & obj_AllExportParameters.vCenterHost & Chr(34) & ","), True)
    obj_AllExportParameters.vCenterSslNoCheckHostname = True
    s_APIBody.AppendLine((Chr(34) & "vCenterSslNoCheckHostname" & Chr(34) & ": " & Chr(34) & obj_AllExportParameters.vCenterSslNoCheckHostname & Chr(34) & ","), True)
    obj_AllExportParameters.platformCredentialId = s_VSPCredID
    s_APIBody.AppendLine((Chr(34) & "platformCredentialId" & Chr(34) & ": " & Chr(34) & obj_AllExportParameters.platformCredentialId & Chr(34) & ","), True)
    obj_AllExportParameters.datacenter = obj_Export.datacenter
    s_APIBody.AppendLine((Chr(34) & "datacenter" & Chr(34) & ": " & Chr(34) & obj_AllExportParameters.datacenter & Chr(34) & ","), True)
    obj_AllExportParameters.network = obj_Export.network
    s_APIBody.AppendLine((Chr(34) & "network" & Chr(34) & ": " & Chr(34) & obj_AllExportParameters.network & Chr(34) & ","), True)
    obj_AllExportParameters.datastore = obj_Export.datastore
    s_APIBody.AppendLine((Chr(34) & "datastore" & Chr(34) & ": " & Chr(34) & obj_AllExportParameters.datastore & Chr(34) & ","), True)
    obj_AllExportParameters.cluster = obj_Export.cluster
    s_APIBody.AppendLine((Chr(34) & "cluster" & Chr(34) & ": " & Chr(34) & obj_AllExportParameters.cluster & Chr(34) & ","), True)
    obj_AllExportParameters.outputImageFilename = obj_Export.outputImageFilename
    s_APIBody.AppendLine((Chr(34) & "outputImageFilename" & Chr(34) & ": " & Chr(34) & obj_AllExportParameters.outputImageFilename & Chr(34) & ","), True)
    s_APIBody.AppendLine((Chr(34) & "outputStorageLocation" & Chr(34) & ": {"), True)
    obj_AllExportParameters.outputStorageLocation.credentialId = s_SMBCredID
    s_APIBody.AppendLine((Chr(34) & "credentialId" & Chr(34) & ": " & Chr(34) & obj_AllExportParameters.outputStorageLocation.credentialId & Chr(34) & ","), True)
    obj_AllExportParameters.outputStorageLocation.type = obj_Export.smb.type
    s_APIBody.AppendLine((Chr(34) & "type" & Chr(34) & ": " & Chr(34) & obj_AllExportParameters.outputStorageLocation.type & Chr(34) & ","), True)
    obj_AllExportParameters.outputStorageLocation.host = obj_Export.smb.host
    s_APIBody.AppendLine((Chr(34) & "host" & Chr(34) & ": " & Chr(34) & obj_AllExportParameters.outputStorageLocation.host & Chr(34) & ","), True)
    obj_AllExportParameters.outputStorageLocation.sharePath = obj_Export.smb.sharePath
    s_APIBody.AppendLine((Chr(34) & "sharePath" & Chr(34) & ": " & Chr(34) & obj_AllExportParameters.outputStorageLocation.sharePath & Chr(34)), True)
    s_APIBody.AppendLine(("},"), True)
    obj_AllExportParameters.platform = "vSphere"
    s_APIBody.AppendLine((Chr(34) & "platform" & Chr(34) & ": " & Chr(34) & obj_AllExportParameters.platform & Chr(34) & ","), True)
    obj_AllExportParameters.prefix = obj_Export.prefix
    s_APIBody.AppendLine((Chr(34) & "prefix" & Chr(34) & ": " & Chr(34) & obj_AllExportParameters.prefix & Chr(34) & ","), True)
    obj_AllExportParameters.resourceLocationId = s_RLID
    s_APIBody.AppendLine((Chr(34) & "resourceLocationId" & Chr(34) & ": " & Chr(34) & obj_AllExportParameters.resourceLocationId & Chr(34) & ","), True)
    obj_AllExportParameters.sourceDiskName = obj_Export.sourceDiskName
    s_APIBody.AppendLine((Chr(34) & "sourceDiskName" & Chr(34) & ": " & Chr(34) & obj_AllExportParameters.sourceDiskName & Chr(34) & ","), True)
    obj_AllExportParameters.timeoutInSeconds = obj_Export.timeoutInSeconds
    s_APIBody.AppendLine((Chr(34) & "timeoutInSeconds" & Chr(34) & ": " & Chr(34) & obj_AllExportParameters.timeoutInSeconds & Chr(34)), True)
    s_APIBody.AppendLine(("}"), True)
End Sub

Private Sub but_export_Click(sender As Object, e As EventArgs) Handles but_export.Click
    Dim rest As New Chilkat.Rest
    ' Connect to the CCAPI server.
    Dim bTls As Boolean = True
    Dim port As Integer = 443
    Dim bAutoReconnect As Boolean = True
    Dim success As Boolean = rest.Connect("api.eu.layering.cloud.com", port, bTls, bAutoReconnect)
    If (success <> True) Then
        Debug.WriteLine(rest.LastErrorText)
        Exit Sub
    End If
    success = rest.AddHeader("Citrix-CustomerId", obj_CCAPI_Token.CustomerID)
    success = rest.AddHeader("Authorization", "CWSAuth bearer=" & obj_BearerToken.AccessToken.ToString)
    success = rest.AddHeader("Accept", "application/json")
    success = rest.AddHeader("Content-Type", "application/json")
    Dim APIPostCallPath As String = "/Images/$export?async=true"
    Dim s_Response As String = Nothing
    s_APIResponse = New Chilkat.StringBuilder
    Dim s_REsp As String
    Try
        success = rest.FullRequestSb("POST", APIPostCallPath, s_APIBody, s_APIResponse)
        s_REsp = s_APIResponse.GetAsString
        obj_ExportResponse = New CCAPI_ExportResponse
        obj_ExportResponse = JsonConvert.DeserializeObject(Of CCAPI_ExportResponse)(s_REsp, JSONSettings)
        ‘Enable timer to get progress of asynchronous process
        timer_progress.Enabled = True

    Catch exc As Exception
        Console.WriteLine(exc.ToString)

    End Try

End Sub

Invoke PowerShell to upload Image to Azure

Private Sub Invoke_PowerShellToUpload()
    Dim sessionState = InitialSessionState.CreateDefault()
    sessionState.ExecutionPolicy = Microsoft.PowerShell.ExecutionPolicy.Unrestricted

    Using powershell As PowerShell = PowerShell.Create(sessionState)
        powershell.AddCommand("Copy-DiskToAzure")
        powershell.AddParameter("Filename", obj_AllUploadParams.Filename)
        powershell.AddParameter("ManagedDiskName", obj_AllUploadParams.ManagedDiskname)
        powershell.AddParameter("SubscriptionId", obj_AllUploadParams.SubscriptionID)
        powershell.AddParameter("Location", obj_AllUploadParams.Location)
        powershell.AddParameter("ResourceGroup", obj_AllUploadParams.ResourceGroup)
        powershell.AddParameter("Timeout", obj_AllUploadParams.Timeout)
        lbl_upload.Text = "Uploading..."
        pb_upload.Value = 25
***** SNippet mus be completed, sync to GitHub failed *****
     End Using

End Sub

Start preparation of Image

Private Sub but_prepareVHD_Click(sender As Object, e As EventArgs) Handles but_prepareVHD.Click
        Dim rest As New Chilkat.Rest

        ' Connect to the CCAPI server.
        Dim bTls As Boolean = True
        Dim port As Integer = 443
        Dim bAutoReconnect As Boolean = True

        Dim success As Boolean = rest.Connect("api.eu.layering.cloud.com", port, bTls, bAutoReconnect)
        If (success <> True) Then
            Debug.WriteLine(rest.LastErrorText)
            Exit Sub
        End If

        success = rest.AddHeader("Citrix-CustomerId", obj_CCAPI_Token.CustomerID)
        success = rest.AddHeader("Authorization", "CWSAuth bearer=" & obj_BearerToken.AccessToken.ToString)
        success = rest.AddHeader("Accept", "application/json")
        success = rest.AddHeader("Content-Type", "application/json")

        Dim APIPostCallPath As String = "/images/$prepare?async=true"
        Dim s_Response As String = Nothing

        txtParams(0) = txt_status
        txtParams(1) = "Sending REST-Call to prepare Image to SMB share..." & vbCrLf
        Me.Invoke(New WriteTextDelegate(AddressOf WriteText), txtParams)

        s_APIResponse = New Chilkat.StringBuilder
        Dim s_REsp As String

        Try
            success = rest.FullRequestSb("POST", APIPostCallPath, s_APIBody, s_APIResponse)
            s_REsp = s_APIResponse.GetAsString
            obj_PrepareResponse = New CCAPI_PrepareResponse
            obj_PrepareResponse = JsonConvert.DeserializeObject(Of CCAPI_PrepareResponse)(s_REsp, JSONSettings)
            timer_prepare.Enabled = True

        Catch exc As Exception
            Console.WriteLine(exc.ToString)

        End Try

    End Sub
Public Sub GetPrepareProgress(ByVal obj_CCAPI_Token As CCAPI_Token, ByVal obj_PrepareResponse As CCAPI_PrepareResponse)
        pb_prepare.Value = 5

        Dim rest As New Chilkat.Rest

        ' Connect to the CCAPI server.
        Dim bTls As Boolean = True
        Dim port As Integer = 443
        Dim bAutoReconnect As Boolean = True

        Dim success As Boolean = rest.Connect("api.eu.layering.cloud.com", port, bTls, bAutoReconnect)
        If (success <> True) Then
            Debug.WriteLine(rest.LastErrorText)
            Exit Sub
        End If

        success = rest.AddHeader("Citrix-CustomerId", obj_CCAPI_Token.CustomerID)
        success = rest.AddHeader("Authorization", "CWSAuth bearer=" & obj_BearerToken.AccessToken.ToString)
        success = rest.AddHeader("Accept", "application/json")
        success = rest.AddHeader("Content-Type", "application/json")

        Dim APIPostCallPath As String = "/jobs/" & obj_PrepareResponse.id
        Dim s_Response As String = Nothing

        Dim i_Progress As Integer

        Try
            s_Response = rest.FullRequestFormUrlEncoded("GET", APIPostCallPath)

            's_REsp = s_APIResponse.GetAsString
            obj_JobProgressResponse = New cc
            obj_JobProgressResponse = JsonConvert.DeserializeObject(Of CCAPI_JobProgressResponse)(s_Response, JSONSettings)
            'GetExportProgress(obj_CCAPI_Token, obj_ExportResponse)

            i_OverallProgress = CInt(obj_JobProgressResponse.overallProgressPercent)
            If i_OverallProgress <> 0 Then
                pb_prepare.Value = i_OverallProgress

            End If
            s_ProgressStatus = obj_JobProgressResponse.status
            lbl_progressstatus.Text = "Progress-Status: " & s_ProgressStatus

            If obj_JobProgressResponse.error.Length <> 0 Then
                Dim s_error As String
                s_error = obj_JobProgressResponse.error(0).ToString
                pb_prepare.BackColor = Color.Red
                pb_prepare.ForeColor = Color.Red

                pb_prepare.Value = 100
                lbl_progressstatus.Text = "Error - see logs!"
                timer_progress.Enabled = False
                txtParams(0) = txt_status
                txtParams(1) = "Export failed - see logs!..." & vbCrLf
                Me.Invoke(New WriteTextDelegate(AddressOf WriteText), txtParams)
                txtParams(0) = txt_status
                txtParams(1) = "-------------------------------" & vbCrLf & vbCrLf
                Me.Invoke(New WriteTextDelegate(AddressOf WriteText), txtParams)

            End If

            If s_ProgressStatus = "Complete" Then

                pb_prepare.Value = 100
                lbl_progressstatus.Text = s_ProgressStatus
                timer_progress.Enabled = False
                txtParams(0) = txt_status
                txtParams(1) = "Export successful!" & vbCrLf
                Me.Invoke(New WriteTextDelegate(AddressOf WriteText), txtParams)
                txtParams(0) = txt_status
                txtParams(1) = "-------------------------------" & vbCrLf & vbCrLf
                Me.Invoke(New WriteTextDelegate(AddressOf WriteText), txtParams)

            End If

            ' s_ProgressError = obj_JobProgressResponse.error

        Catch exc As Exception
            Console.WriteLine(exc.ToString)

        End Try
    End Sub

User Feedback


There are no comments to display.



Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×
×
  • Create New...