Jump to content
Updated Privacy Statement

Citrix DaaS and Terraform - Automatic Deployment of a Resource Location on Google Cloud Platform (GCP)

  • Contributed By: Gerhard Krenn Special Thanks To: Jason Samuel, Avijit Gahtori, Zhuolun Liu

 

Overview

This guide aims to provide an overview of using Terraform to create a complete Citrix DaaS Resource Location on Google Cloud Platform (GCP).
At the end of the process, you created:

  • A new Citrix Cloud Resource Location (RL) running on Google Cloud Platform (GCP)
  • 2 Cloud Connector Virtual Machines registered with the Domain and the Resource Location
  • A Hypervisor Connection and a Hypervisor Pool pointing to the new Resource Location in Google Cloud Platform (GCP)
  • A Machine Catalog based on the uploaded Master Image VHD or on a Google Cloud Platform (GCP)-based Master Image
  • A Delivery Group based on the Machine Catalog with full Autoscale Support
  • Example policies and policy scopes bound to the Delivery Group

 

Note:

This guide aims at using Google Cloud Platform (GCP) as a Resource Location for Citrix DaaS.
Of course Google Cloud Platform (GCP) and other supported HyperScalers can also be used for on-premises Citrix Virtual Apps And Desktops (CVAD) customers.
Guides for Virtual Apps And Desktops (CVAD) customers using HyperScalers together with Terraform will be provided soon!


What is Terraform

Terraform is an Infrastructure-as-Code (IaC) tool that defines cloud and on-prem resources in easy-readable configuration files rather than through a GUI.

IaC allows you to build, change, and manage your infrastructure safely and consistently by defining resource configurations.
These configurations can be versioned, reused, and shared and are created in its native declarative configuration language known as HashiCorp Configuration Language (HCL), or optionally using JSON.

Terraform creates and manages resources on Cloud platforms and other services through their application programming interfaces (APIs). Terraform providers are compatible with virtually any platform or service with an accessible API.

terraform-gcp-scheme.png

More information about Terraform can be found at https://developer.hashicorp.com/terraform/intro.

Installation

HashiCorp distributes Terraform as a binary package. You can also install Terraform using popular package managers. In this example, we use Chocolatey for Windows to deploy Terraform. Chocolatey is a free and open-source package management system for Windows. Install the Terraform package from the CLI.

Installation of Chocolatey

Open a PowerShell shell with Administrative rights and paste the following command:

Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))

Chocolatey downloads and installs all necessary components automatically:

PS C:\TACG> Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString (https://community.chocolatey.org/install.ps1))       
                                                                
 Forcing web requests to allow TLS v1.2 (Required for requests to Chocolatey.org)
 Getting latest version of the Chocolatey package for download.                                                         
 Not using proxy.    
                                                                                                   
 Getting Chocolatey from https://community.chocolatey.org/api/v2/package/chocolatey/2.2.2.
 Downloading https://community.chocolatey.org/api/v2/package/chocolatey/2.2.2 to C:\TACG\AppData\Local\Temp\chocolatey\chocoInstall\chocolatey.zip
 Not using proxy.

 Extracting C:\TACG\AppData\Local\Temp\chocolatey\chocoInstall\chocolatey.zip to C:\TACG\AppData\Local\Temp\chocolatey\chocoInstall
 
Installing Chocolatey on the local machine
 Creating ChocolateyInstall as an environment variable (targeting 'Machine')
   Setting ChocolateyInstall to 'C:\ProgramData\chocolatey'
 WARNING: It's very likely you will need to close and reopen your shell before you can use choco.
 Restricting write permissions to Administrators
 We are setting up the Chocolatey package repository.
 The packages themselves go to 'C:\ProgramData\chocolatey\lib'
   (i.e. C:\ProgramData\chocolatey\lib\yourPackageName).
 A shim file for the command line goes to 'C:\ProgramData\chocolatey\bin' and points to an executable in 'C:\ProgramData\chocolatey\lib\yourPackageName'.

 Creating Chocolatey folders if they do not already exist.

 chocolatey.nupkg file not installed in lib.
  Attempting to locate it from bootstrapper.
 PATH environment variable does not have C:\ProgramData\chocolatey\bin in it. Adding...
 WARNING: Not setting tab completion: Profile file does not exist at
 'C:\TACG\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1'.

 Chocolatey (choco.exe) is now ready.
 You can call choco from anywhere, command line or powershell by typing choco.
 Run choco /? for a list of functions.
 You may need to shut down and restart powershell and/or consoles first prior to using choco.
 Ensuring Chocolatey commands are on the path
 Ensuring chocolatey.nupkg is in the lib folder

PS C:\TACG>


Run choco --v to check if Chocolatey was installed successfully:

PS C:\TACG> choco --v
 Chocolatey v2.2.2

 PS C:\TACG>
 
Installation of Terraform

After the successful installation of Chocolatey, you can install Terraform by running this command on the PowerShell session:

choco install terraform

 

PS C:\TACG> choco install terraform
 Chocolatey v2.2.2
 Installing the following packages:
 terraform
 By installing, you accept licenses for the packages.
 Progress: Downloading terraform 1.6.4... 100%

 terraform v1.7.4 [Approved]
 terraform package files install completed. Performing other installation steps.
 The package terraform wants to run 'chocolateyInstall.ps1'.
 Note: If you don't run this script, the installation will fail.
 Note: To confirm automatically next time, use '-y' or consider:
 choco feature enable -n allowGlobalConfirmation
 Do you want to run the script?([Y]es/[A]ll - yes to all/[N]o/[P]rint): A

 Removing old terraform plug-ins
 Downloading terraform 64 bit from 'https://releases.hashicorp.com/terraform/1.7.4/terraform_1.7.4_windows_amd64.zip'
 Progress: 100% - Completed download of C:\TACG\AppData\Local\Temp\chocolatey\terraform\1.7.4\terraform_1.7.4_windows_amd64.zip (25.05 MB).
 Download of terraform_1.7.4_windows_amd64.zip (25.05 MB) completed.
 Hashes match.
 
Extracting C:\TACG\AppData\Local\Temp\chocolatey\terraform\1.7.4\terraform_1.7.4_windows_amd64.zip to C:\ProgramData\chocolatey\lib\terraform\tools...
 C:\ProgramData\chocolatey\lib\terraform\tools
  ShimGen has successfully created a shim for terraform.exe

  The install of terraform was successful.
  Software installed to 'C:\ProgramData\chocolatey\lib\terraform\tools'

 Chocolatey installed 1/1 packages.
 See the log for details (C:\ProgramData\chocolatey\logs\chocolatey.log).
PS C:\TACG>
 

Run terraform -version to check if Terraform was installed successfully:
 
PS C:\TACG> terraform -version
 Terraform v1.7.4 on windows_amd64
 
PS C:\TACG>

The installation of Terraform is now completed.


Terraform - Basics and Commands

Terraform Block

The terraform {} block contains Terraform settings, including the required providers to provision your infrastructure. Terraform installs providers from the Terraform Registry.

Providers

The provider block configures the specified provider. A provider is a plug-in that Terraform uses to create and manage your resources. Providing multiple provider blocks in the Terraform configuration enables managing resources from different providers.

Resources

Resource blocks define the components of the infrastructure - physical, virtual, or logical. These blocks contain arguments to configure the resource. The provider's reference lists the required and optional arguments for each resource.

The core Terraform workflow consists of three stages

Write:
You define resources that are deployed, altered, or deleted.

Plan:
Terraform creates an execution plan describing the infrastructure it creates, updates, or destroys based on the existing infrastructure and your configuration.

Apply:
On approval, Terraform does the proposed operations in the correct order, respecting any resource dependencies.

Terraform does not only add complete configurations, it also allows you to change previously added configurations.
For example, changing the DNS servers of a NIC of a Google Cloud Platform (GCP) VM does not require redeploying the whole configuration - Terraform only alters the needed resources.

Terraform Provider for Citrix

Citrix has developed a custom Terraform provider for automating Citrix product deployments and configurations.

Note:

The Citrix Terraform provider is currently in Tech Preview!
This guide is updated when the provider has reached RTM.

You can use Terraform with Citrix's provider to manage your Citrix products via Infrastructure as Code. Terraform provides higher efficiency and consistency in infrastructure management and better reusability in infrastructure configuration.

  • The provider defines individual units of infrastructure and currently supports both Citrix Virtual Apps and Desktops and Citrix DaaS solutions.
  • You can automate the creation of a site setup including host connections, machine catalogs, and delivery groups.
  • You can deploy resources in Google Cloud Platform (GCP), AWS, and Azure, as well as supported on-premises Hypervisors.

Note:

All information about the Citrix Terraform provider can be found on GitHub: https://github.com/citrix/terraform-provider-citrix

terraform-gcp-github.png
 

Terraform expects to be invoked from a working directory that contains configuration files written in the Terraform language.
Terraform uses configuration content from this directory and also uses the directory to store settings, cached plug-ins and modules, and state data.

A working directory must be initialized before Terraform can do any operations.
Initialize the working directory by using the command:

terraform init

 

PS C:\TACG> terraform init

 Initializing the backend...

 Successfully configured the backend "local"! Terraform will automatically use this backend unless the backend configuration changes.

 Initializing provider plug-ins...
 - Finding citrix/citrix versions matching ">= 0.5.4"...
 - Installing citrix/citrix v0.5.4...
 - Installed citrix/citrix v0.5.4 (signed by a HashiCorp partner, key ID 25D62DD8407EA386)

 Partner and community providers are signed by their developers.
 If you'd like to know more about provider signing, you can read about it here:
 https://www.terraform.io/docs/cli/plug-ins/signing.html

 Terraform has created a lock file .terraform.lock.hcl to record the provider selections it made above. Include this file in your version control repository so that Terraform can guarantee to make the same selections by default when you run "terraform init" in the future.

 Terraform has been successfully initialized!

 You may now begin working with Terraform. Try running "terraform plan" to see any changes that are required for your infrastructure. 
 All Terraform commands should now work.

 If you ever set or change modules or backend configuration for Terraform, rerun this command to reinitialize your working directory. 
 If you forget, other commands will detect it and remind you to do so if necessary.

PS C:\TACG>

The provider defines how Terraform can interact with the underlying API. Configurations must declare which providers they require so Terraform can install and use them.

Terraform CLI finds and installs providers when initializing a working directory. It can automatically download providers from a Terraform registry or load them from a local mirror or cache.

Example: Terraform configuration files - provider.tf

The file provider.tf contains the information on the target site where to apply the configuration.
Depending on whether it is a Citrix Cloud site or a Citrix On-Premises site, the provider needs to be configured differently:

terraform {
    required_version = ">= 1.7.4"
 
  required_providers {
    citrix = {
      source  = "citrix/citrix"
      version = ">=0.5.4"
    }
  }
}
 
# Configure the Citrix Cloud Provider
provider "citrix" {
  customer_id   = "${var.CC_CustomerID}"  
  client_id     = "${var.CC_APIKey-ClientID}"  
  client_secret = "${var.CC_APIKey-ClientSecret}"  
}

A guide for creating a secure API client can be found in Citrix Developer Docs and will be shown later.

terraform {
    required_version = ">= 1.7.4"
 
  required_providers {
    citrix = {
      source  = "citrix/citrix"
      version = ">=0.5.4"
    }
  }
}
 
# Configure the Citrix On-Premises Provider
provider "citrix" {
  hostname      = "${var.CVAD_DDC_HostName}"  
  client_id     = "${var.CVAD_Admin_Account_UN}" #Domain\\Username  
  client_secret = "${var.CVAD_Admin_Account_Password}}"  
}

 

Example - Schema used for the Provider configuration
  • client_id (String): Client-ID for Citrix DaaS service authentication
    For Citrix On-Premises customers: Use this variable to specify the Domain-Admin Username.
    For Citrix Cloud customers: Use this variable to specify Cloud API Key Client ID.
    Can be set via the Environment Variable CITRIX_CLIENT_ID.

  • client_secret (String, Sensitive): Client Secret for Citrix DaaS service authentication
    For Citrix On-Premises customers: Use this variable to specify the Domain-Admin Password.
    For Citrix Cloud customers: Use this variable to specify Cloud API Key Client Secret.
    Can be set via the Environment Variable CITRIX_CLIENT_SECRET.

  • customer_id (String): Citrix Cloud customer ID
    Only applicable for Citrix Cloud customers.
    Can be set via the Environment Variable CITRIX_CUSTOMER_ID.

  • disable_ssl_verification (Boolean): Disable SSL verification against the target DDC
    Only applicable to on-premises customers. Citrix Cloud customers do not need this option. Set to true to skip SSL verification only when the target DDC does not have a valid SSL certificate issued by a trusted CA.
    When set to true, please make sure that your provider config is set for a known DDC hostname.
    It is recommended to configure a valid certificate for the target DDC.
    Can be set via the Environment Variable CITRIX_DISABLE_SSL_VERIFICATION.

  • environment (String): Citrix Cloud environment of the customer
    Only applicable for Citrix Cloud customers. Available options: Production, Staging, Japan, JapanStaging.
    Can be set via the Environment Variable CITRIX_ENVIRONMENT.

  • hostname (String) : Hostname/base URL of Citrix DaaS service
    For Citrix On-Premises customers (Required): Use this variable to specify the Delivery Controller hostname.
    For Citrix Cloud customers (Optional): Use this variable to override the Citrix DaaS service hostname.
    Can be set via the Environment Variable CITRIX_HOSTNAME.

Note:

All information about the Citrix Terraform provider can be found on GitHub.

 

Deploying a Citrix Cloud Resource location on Google Cloud Platform (GCP) using Terraform

Overview

This guide showcases the possibility of creating a complete Citrix Cloud Resource Location on Google Cloud Platform (GCP) using Terraform. We want to reduce manual interventions to the absolute minimum.

All Terraform configuration files can be found later on GitHub - we will update this guide as soon as the GitHub repository is ready.

In this guide, we will use an existing domain and will not deploy a new domain. For further instructions on deploying a new domain, please refer to the guide Citrix DaaS and Terraform—Automatic Deployment of a Resource Location on Microsoft Azure. Please note that this guide will be reworked soon!

The AD deployment used for this guide consists of a Hub-and-Spoke model - each Resource Location running on a Hypervisor/Hyperscaler is connected to the main Domain Controller using an IPSec-based Site-to-Site VPN. Each Resource Location has its own sub-domain.

NOTE: For easier reading and understanding, we did not use any loop-constructs or count keywords - each resource is created explicitly.

The Terraform flow is split into different parts:

  • Part One - this part can be run on any computer where Terraform is installed :
    • Creating the initially needed Resources on Google Cloud Platform (GCP) :
      • Creating all needed Firewall rules and Tags on Google Cloud Platform (GCP)
      • Creating all needed PowerShell scripts on Google Cloud Platform (GCP)
      • Creating all needed IP- and DHCP configurations on Google Cloud Platform (GCP)
      • Creating a needed Storage Bucket and its configuration on Google Cloud Platform (GCP)
      • Creating a Windows Server 2022-based Master Image VM used for deploying the Machine Catalog in step 3
      • Creating two Windows Server 2022-based VMs, which will be used as Cloud Connector VMs in Step 2
      • Creating a Windows Server 2022-based VM acting as an Administrative workstation for running Terraform steps 2 and 3—this is necessary because you will use WinRM for further configuration and deployment in steps 2 and 3!
      • Creating all necessary scripts for joining the VMs to the existing sub-domain
      • Putting the VMs into the existing sub-domain

NOTE: The Master Image needs to be configured before it can be deployed - Terraform will configure a blank, Domain-joined VM! The Administrative VM must have Terraform installed - use the Chocolately-Guidance provided previously for installation

  • Part Two - this part can only be run on the previously created Administrative VM as the deployment of steps 2 and 3 relies heavily on WinRM:

    • Configuring the three previously created Virtual Machines on Google Cloud Platform (GCP):

      • Installing the needed software on the CCs
      • Installing the needed software on the Admin-VM
    • Creating the necessary Resources in Citrix Cloud:

      • Creating a Resource Location in Citrix Cloud
      • Uploading all relevant configurations to the Cloud Connector VMs
      • Configuring the 2 Cloud Connectors
      • Registering the 2 Cloud Connectors in the newly created Resource Location
  • Part Three:

    • Creating the Machine Catalog and Delivery Group in Citrix Cloud:
      • Retrieving the Site- and Zone-ID of the Resource Location
      • Creating a dedicated Hypervisor Connection to Google Cloud Platform (GCP)
      • Creating a dedicated Hypervisor Resource Pool
      • Creating a Machine Catalog (MC) in the newly created Resource Location
      • Creating a Delivery Group (DG) based on the MC in the newly created Resource Location
      • Setting the AutoScale configuration for the created Delivery Group
      • Deploying some sample policies and binding them to the created Delivery Group

NOTE: Please make sure that all Terraform-related VMs can communicate using WinRM.
If the Admin-VM is not able to connect to the Cloud Connectors using WinRM, the deployment will fail.
Various configuration guides for WinRM can be found on the Internet.

Determine if WinRM connections/communications are functioning

We strongly recommend a quick check to determine the communication before starting the Terraform scripts:

Open a PowerShell console and type:

test-wsman -ComputerName <IP-Address of the computer you want to reach> -Credential <IP-Address of the computer you want to reach>\administrator -Authentication Basic 

The response should look like:  
wsmid           : http://schemas.dmtf.org/wbem/wsman/identity/1/wsmanidentity.xsd  
ProtocolVersion : http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd  
ProductVendor   : Microsoft Corporation  
ProductVersion  : OS: 0.0.0 SP: 0.0 Stack: 3.0  

Another possibility is to open a PowerShell console and type:  
Enter-PSSession -ComputerName <IP-Address of the computer you want to reach> -Credential <IP-Address of the computer you want to reach>\administrator  

The response should look like:  
[10.156.0.5]: PS C:\Users\Administrator\Documents>

 

A short Terraform script also checks if the communication via WinRM between the Admin-VM and, in this example, the CC1-VM is working as intended:

locals {
  #### Test the WinRM communication
  #### Need to invoke PowerShell as Domain User as the provisioner does not allow to be run in a Domain Users-context
  TerraformTestWinRMScript     = <<-EOT
  $PSUsername = '${var.Provisioner_DomainAdmin-Username}'
  $PSPassword = '${var.Provisioner_DomainAdmin-Password}'
  $PSSecurePassword = ConvertTo-SecureString $PSPassword -AsPlainText -Force
  $PSCredential = New-Object System.Management.Automation.PSCredential ($PSUsername, $PSSecurePassword)
  Invoke-Command -ComputerName localhost -Credential $PSCredential -ScriptBlock {
  $FileNameForData = 'C:\temp\xdinst\Processes.txt'
  If (Test-Path $FileNameForData) {Remove-Item -Path $FileNameForData -Force}
  Get-Process | Out-File -FilePath 'C:\temp\xdinst\Processes.txt'
  }
  EOT
}
 
#### Write script into local data-directory
resource "local_file" "WriteWinRMTestScriptIntoDataDirectory" {
  filename = "${path.module}/data/Terraform-Test-WinRM.ps1"
  content  = local.TerraformTestWinRMScript
}
 
resource "null_resource" "CreateTestScriptOnCC1" {
 
connection {
    type            = var.Provisioner_Type
    user            = var.Provisioner_Admin-Username
    password        = var.Provisioner_Admin-Password
    host            = var.Provisioner_CC1-IP
    timeout         = var.Provisioner_Timeout
 
  }
 
   provisioner "file" {
    source      = "${path.module}/data/Terraform-Test-WinRM.ps1"
    destination = "C:/temp/xdinst/Terraform-Test-WinRM.ps1"
   
  }
 
  provisioner "remote-exec" {
    inline = [
      "powershell -File 'C:/temp/xdinst/Terraform-Test-WinRM.ps1'"
    ]
  }
}

If you can see something like the output below...

null_resource.CreateTestScriptOnCC1: Creating...
null_resource.CreateTestScriptOnCC1: Provisioning with 'remote-exec'...
null_resource.CreateTestScriptOnCC1 (remote-exec): Connecting to remote host via WinRM...
null_resource.CreateTestScriptOnCC1 (remote-exec):   Host: 10.156.0.5
null_resource.CreateTestScriptOnCC1 (remote-exec):   Port: 5985
null_resource.CreateTestScriptOnCC1 (remote-exec):   User: administrator
null_resource.CreateTestScriptOnCC1 (remote-exec):   Password: true
null_resource.CreateTestScriptOnCC1 (remote-exec):   HTTPS: false
null_resource.CreateTestScriptOnCC1 (remote-exec):   Insecure: false
null_resource.CreateTestScriptOnCC1 (remote-exec):   NTLM: false
null_resource.CreateTestScriptOnCC1 (remote-exec):   CACert: false
null_resource.CreateTestScriptOnCC1 (remote-exec): Connected!  

#< CLIXML
<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04"><Obj S="progress" RefId="0"><TN RefId="0"><T>System.Management.Automation.PSCustomObject</T><T>System.Object</T></TN><MS><I64 N="SourceId">1</I64><PR N="Record"><AV>Preparing modules for first use.</AV><AI>0</AI><Nil /><PI>-1</PI><PC>-1</PC><T>Completed</T><SR>-1</SR><SD> </SD></PR></MS></Obj></Objs>#< CLIXML
<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04"><Obj S="progress" RefId="0"><TN RefId="0"><T>System.Management.Automation.PSCustomObject</T><T>System.Object</T></TN><MS><I64 N="SourceId">1</I64><PR N="Record"><AV>Preparing modules for first use.</AV><AI>0</AI><Nil /><PI>-1</PI><PC>-1</PC><T>Completed</T><SR>-1</SR><SD> </SD></PR></MS></Obj></Objs>
null_resource.CreateTestScriptOnCC1 (remote-exec): C:\Users\Administrator>powershell -File c:/temp/xdinst/DATA/Terraform-Test-WinRM.ps1

#< CLIXML
<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04"><Obj S="progress" RefId="0"><TN RefId="0"><T>System.Management.Automation.PSCustomObject</T><T>System.Object</T></TN><MS><I64 N="SourceId">1</I64><PR N="Record"><AV>Preparing modules for first use.</AV><AI>0</AI><Nil /><PI>-1</PI><PC>-1</PC><T>Completed</T><SR>-1</SR><SD> </SD></PR></MS></Obj><Obj S="progress" RefId="1"><TNRef RefId="0" /><MS><I64 N="SourceId">1</I64><PR N="Record"><AV>Preparing modules for first use.</AV><AI>0</AI><Nil /><PI>-1</PI><PC>-1</PC><T>Completed</T><SR>-1</SR><SD> </SD></PR></MS></Obj></Objs>#< CLIXML
<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04"><Obj S="progress" RefId="0"><TN RefId="0"><T>System.Management.Automation.PSCustomObject</T><T>System.Object</T></TN><MS><I64 N="SourceId">1</I64><PR N="Record"><AV>Preparing modules for first use.</AV><AI>0</AI><Nil /><PI>-1</PI><PC>-1</PC><T>Completed</T><SR>-1</SR><SD> </SD></PR></MS></Obj></Objs>null_resource.CreateTestScriptOnCC1: Creation complete after 3s [id=394829982371734209]

...then you can be sure that the provisioning using WinRM is working as intended!
 

Configuration using variables

All needed configuration settings are stored in the corresponding Variables that need to be set. Some Configuration settings are propagated throughout the whole Terraform configuration...

You need to start each of the 3 modules manually using the Terraform workflow terraform init, terraform plan, and terraform apply in the corresponding module directory. Terraform then completes the necessary configuration steps of the corresponding module.

NOTE: Each module must be completed successfully before the next module can be started!


File System structure

Root-Directory

Module 1: _CConGCP-Creation:

Filename Purpose
_CConGCP-Creation-Create.tf Resource configuration and primary flow definition
_CConGCP-Creation-Create-variables.tf Definition of Variables
_CConGCP-Creation-Create.auto.tfvars.json Setting the values of the Variables
_CConGCP-Creation-Provider.tf Provider definition and configuration
_CConGCP-Creation-Provider-variables.tf Definition of Variables
_CConGCP-Creation-Provider.auto.tfvars.json Setting the values of the Variables
TF-Domain-Join-Script.ps1 Powershell-Script for joining the Admin-VM to the Domain
DATA-Directory All other needed files are placed in here (see later)

Module 2: _CConGCP-Install:

Filename Purpose
_CConGCP-Install-CreatePreReqs.tf Resource configuration and primary flow definition
_CConGCP-Install-CreatePreReqs-variables.tf Definition of Variables
_CConGCP-Install-CreatePreReqs.auto.tfvars.json Setting the values of the Variables
_CConGCP-Install-Provider.tf Provider definition and configuration
_CConGCP-Install-Provider-variables.tf Definition of Variables
_CConGCP-Install-Provider.auto.tfvars.json Setting the values of the Variables

Module 3: _CConGCP-CCStuff:

Filename Purpose
_CConGCP-CCStuff-CreateCCEntities.tf Resource configuration and primary flow definition
_CConGCP-CCStuff-CreateCCEntities-variables.tf Definition of Variables
_CConGCP-CCStuff-CreateCCEntities.auto.tfvars.json Setting the values of the Variables
_CConGCP-CCStuff-CreateCCEntities-GCP.auto.tfvars.json Extracted Service Account details for further use
_CConGCP-CCStuff-Provider.tf Provider definition and configuration
_CConGCP-CCStuff-Provider-variables.tf Definition of Variables
_CConGCP-CCStuff-Provider.auto.tfvars.json Setting the values of the Variables

var.CC_Install_LogPath-based directory:

Filename Purpose
CitrixPosHSDK.exe DaaS Remote PoSH SDK installer
cwc.json Configuration file for unattended Cloud Connector setup
<gcp-project-id>.json Service Account used for Authentication
GetBT.ps1, CreateRL.ps1, GetSiteID.ps1, GetZoneID.ps1, ... Various PowerShell scripts needed for deployment
GetBT.txt, GetRLID.txt, GetSiteID.txt, GetZoneid.txt, ... txt-files containing the results of Powershell

NOTE: All Terraform-related directories and files (.terraform, -terrafrom.lock.hcl, terraform.tfstate, terraform.tfstate) must not be changed or deleted - doing so might break the deployment!

Change the settings in the .json files according to your needs.

Before setting the corresponding settings or running the Terraform workflow, the following prerequisites are needed to ensure a smooth and error-free build.

Prerequisites

Installing Google Cloud Tools for PowerShell and gcloud CLI

In this guide, we use gcloud CLI and PowerShell cmdlets to determine further needed information.

gcp-install-gcloud.png

gcp-install-ps.png

Further information about gcloud CLI and the installation/configuration can be found at gcloud Command Line Interface, and further information about the PowerShell cmdlets for Google Cloud can be found at Cloud Tools for PowerShell.

After installing gcloud CLI you need to configure it:

PS C:\TACG> gcloud init
Welcome! This command will take you through the configuration of gcloud.

Settings from your current configuration [default] are:
accessibility:
  screen_reader: 'False'
compute:
  region: us-east1
  zone: us-east1-b
core:
  account: XXXXXXXXXX@the-austrian-citrix-guy.at
  disable_usage_reporting: 'True'
  project: tacg-gcp-XXXXXXXXXXXX

Pick configuration to use:
 [1] Re-initialize this configuration [default] with new settings
 [2] Create a new configuration
 [3] Switch to and re-initialize existing configuration: [tacg-gcp-n]
Please enter your numeric choice:  1

Your current configuration has been set to: [default]

You can skip diagnostics next time by using the following flag:
  gcloud init --skip-diagnostics

Network diagnostic detects and fixes local network connection issues.
Checking network connection...done.
Reachability Check passed.
Network diagnostic passed (1/1 checks passed).

Choose the account you would like to use to perform operations for this configuration:
 [1] XXXXXXXXXX@XXXXXXXXXX
 [2] XXXXXXXXXX@the-austrian-citrix-guy.at
 [3] Log in with a new account
Please enter your numeric choice:  2

You are logged in as: [XXXXXXXXXX@the-austrian-citrix-guy.at].

Reauthentication required.
Please enter your password:
Pick cloud project to use:
 [1] tacg-gcp-XXXXXXXXXXXX
 [2] Enter a project ID
 [3] Create a new project
Please enter numeric choice or text value (must exactly match list item):  1

Your current project has been set to: [tacg-gcp-XXXXXXXXXXXX].

Do you want to configure a default Compute Region and Zone? (Y/n)?  Y

Which Google Compute Engine zone would you like to use as project default?
If you do not specify a zone via a command line flag while working with Compute Engine resources, the default is
assumed.
 [1] us-east1-b
 [2] us-east1-c
 [3] us-east1-d
 [4] us-east4-c
 [5] us-east4-b
 [6] us-east4-a
 [7] us-central1-c
 [8] us-central1-a
 [9] us-central1-f
 [10] us-central1-b
 [11] us-west1-b
 [12] us-west1-c
 [13] us-west1-a
 [14] europe-west4-a
 [15] europe-west4-b
 [16] europe-west4-c
 [17] europe-west1-b
 [18] europe-west1-d
 [19] europe-west1-c
 [20] europe-west3-c
 [21] europe-west3-a
 [22] europe-west3-b
 [23] europe-west2-c
 [24] europe-west2-b
 [25] europe-west2-a
 [26] asia-east1-b
 [27] asia-east1-a
 [28] asia-east1-c
 [29] asia-southeast1-b
 [30] asia-southeast1-a
 [31] asia-southeast1-c
 [32] asia-northeast1-b
 [33] asia-northeast1-c
 [34] asia-northeast1-a
 [35] asia-south1-c
 [36] asia-south1-b
 [37] asia-south1-a
 [38] australia-southeast1-b
 [39] australia-southeast1-c
 [40] australia-southeast1-a
 [41] southamerica-east1-b
 [42] southamerica-east1-c
 [43] southamerica-east1-a
 [44] africa-south1-a
 [45] africa-south1-b
 [46] africa-south1-c
 [47] asia-east2-a
 [48] asia-east2-b
 [49] asia-east2-c
 [50] asia-northeast2-a
Did not print [72] options.
Too many options [122]. Enter "list" at prompt to print choices fully.
Please enter numeric choice or text value (must exactly match list item):  20

Your project default Compute Engine zone has been set to [europe-west3-c].
You can change it by running [gcloud config set compute/zone NAME].

Your project default Compute Engine region has been set to [europe-west3].
You can change it by running [gcloud config set compute/region NAME].

Your Google Cloud SDK is configured and ready to use!

* Commands that require authentication will use XXXXXXXXXX@the-austrian-citrix-guy.at by default
* Commands will reference project `tacg-gcp-XXXXXXXXXX` by default
* Compute Engine commands will use region `europe-west3` by default
* Compute Engine commands will use zone `europe-west3-c` by default

Run `gcloud help config` to learn how to change individual settings

This gcloud configuration is called [default]. You can create additional configurations if you work with multiple accounts and/or projects.
Run `gcloud topic configurations` to learn more.

Some things to try next:

* Run `gcloud --help` to see the Cloud Platform services you can interact with. And run `gcloud help COMMAND` to get help on any gcloud command.
* Run `gcloud topic --help` to learn about advanced features of the SDK like arg files and output formatting
* Run `gcloud cheat-sheet` to see a roster of go-to `gcloud` commands.
PS C:\TACG>

 

Enabling all needed APIs

Citrix Cloud interacts with your Google Cloud project by using several different APIs. These APIs aren't necessarily enabled by default, but they're necessary for Citrix Virtual Delivery Agent (VDA) fleet creation and lifecycle management. For Citrix Cloud to function, the following Google APIs must be enabled on your project:

  • Compute Engine API
  • Cloud Resource Manager API
  • Identity and Access Management (IAM) API
  • Cloud Build API
  • Cloud Domain Name System (DNS) API

To enable the APIs, paste the following commands into gcloud CLI:

gcloud services enable compute.googleapis.com

gcloud services enable cloudresourcemanager.googleapis.com

gcloud services enable iam.googleapis.com

gcloud services enable cloudbuild.googleapis.com

gcloud services enable dns.googleapis.com

 

Existing Google Cloud Platform (GCP) entities

We anticipate that the following resources already exist and are already configured on Google Cloud Platform (GCP) :

  • A working tenant
  • All needed rights for the IAM user on the tenant
  • A working Network structure with at least one subnet in the VPC
  • A security group configured for allowing inbound connections from the subnet and partially from the Internet: WinRM-HTTP, WinRM-HTTPS, UDP, DNS (UDP and TCP), ICMP (for testing purposes), HTTP, HTTPS, TCP (for testing purposes), RDP. No blocking rules for outbound connections should be in place
  • An access key with its secret (see a description of how to create the key later on)
  • No bottlenecks/quotas that might block the deployment

You can get the needed information about the Network configuration by using gcloud:

PS C:\TACG> gcloud compute networks list
NAME     SUBNET_MODE  BGP_ROUTING_MODE  IPV4_RANGE  GATEWAY_IPV4
default  AUTO         REGIONAL
PS C:\TACG>

PS C:\TACG> gcloud compute networks subnets list --regions europe-west3
NAME     REGION        NETWORK  RANGE          STACK_TYPE  IPV6_ACCESS_TYPE  INTERNAL_IPV6_PREFIX  EXTERNAL_IPV6_PREFIX
default  europe-west3  default  10.156.0.0/20  IPV4_ONLY

PS C:\TACG> 

PS C:\TACG> gcloud compute networks list --uri
https://www.googleapis.com/compute/v1/projects/tacg-gcp-XXXXXXXXXX/global/networks/default

PS C:\TACG>

PS C:\TACG> gcloud compute networks subnets list --uri --regions europe-west3
https://www.googleapis.com/compute/v1/projects/tacg-gcp-XXXXXXXXXX/regions/europe-west3/subnetworks/default
PS C:\TACG>

Note the Network-Name (in this example default), the Subnet-Name, the Network-URI and the Subnet-URI of the Network configuration you want to use and put it into the corresponding .auto.tvars.json file.

NOTE: Do NOT forget to enable Google Private Access on the corresponding Subnet: You can do it on the Subnet pag or by using gcloud.

gcp-enable-gpa-on-subnet.png

Using gcloud to enable GPA and check if GPA is enabled:

 

PS C:\TACG> gcloud compute networks subnets list --filter=default
NAME     REGION                   NETWORK  RANGE          STACK_TYPE  IPV6_ACCESS_TYPE  INTERNAL_IPV6_PREFIX  EXTERNAL_IPV6_PREFIX
default  us-central1              default  10.128.0.0/20  IPV4_ONLY
default  europe-west1             default  10.132.0.0/20  IPV4_ONLY
default  us-west1                 default  10.138.0.0/20  IPV4_ONLY
default  asia-east1               default  10.140.0.0/20  IPV4_ONLY
default  us-east1                 default  10.142.0.0/20  IPV4_ONLY
default  asia-northeast1          default  10.146.0.0/20  IPV4_ONLY
default  asia-southeast1          default  10.148.0.0/20  IPV4_ONLY
default  us-east4                 default  10.150.0.0/20  IPV4_ONLY
default  australia-southeast1     default  10.152.0.0/20  IPV4_ONLY
default  europe-west2             default  10.154.0.0/20  IPV4_ONLY
default  europe-west3             default  10.156.0.0/20  IPV4_ONLY
default  southamerica-east1       default  10.158.0.0/20  IPV4_ONLY
default  asia-south1              default  10.160.0.0/20  IPV4_ONLY
default  northamerica-northeast1  default  10.162.0.0/20  IPV4_ONLY
default  europe-west4             default  10.164.0.0/20  IPV4_ONLY
default  europe-north1            default  10.166.0.0/20  IPV4_ONLY
default  us-west2                 default  10.168.0.0/20  IPV4_ONLY
default  asia-east2               default  10.170.0.0/20  IPV4_ONLY
default  europe-west6             default  10.172.0.0/20  IPV4_ONLY
default  asia-northeast2          default  10.174.0.0/20  IPV4_ONLY
default  asia-northeast3          default  10.178.0.0/20  IPV4_ONLY
default  us-west3                 default  10.180.0.0/20  IPV4_ONLY
default  us-west4                 default  10.182.0.0/20  IPV4_ONLY
default  asia-southeast2          default  10.184.0.0/20  IPV4_ONLY
default  europe-central2          default  10.186.0.0/20  IPV4_ONLY
default  northamerica-northeast2  default  10.188.0.0/20  IPV4_ONLY
default  asia-south2              default  10.190.0.0/20  IPV4_ONLY
default  australia-southeast2     default  10.192.0.0/20  IPV4_ONLY
default  southamerica-west1       default  10.194.0.0/20  IPV4_ONLY
default  us-east7                 default  10.196.0.0/20  IPV4_ONLY
default  europe-west8             default  10.198.0.0/20  IPV4_ONLY
default  europe-west9             default  10.200.0.0/20  IPV4_ONLY
default  us-east5                 default  10.202.0.0/20  IPV4_ONLY
default  europe-southwest1        default  10.204.0.0/20  IPV4_ONLY
default  us-south1                default  10.206.0.0/20  IPV4_ONLY
default  me-west1                 default  10.208.0.0/20  IPV4_ONLY
default  europe-west12            default  10.210.0.0/20  IPV4_ONLY
default  me-central1              default  10.212.0.0/20  IPV4_ONLY
default  europe-west10            default  10.214.0.0/20  IPV4_ONLY
default  me-central2              default  10.216.0.0/20  IPV4_ONLY
default  africa-south1            default  10.218.0.0/20  IPV4_ONLY
default  us-west8                 default  10.220.0.0/20  IPV4_ONLY

PS C:\TACG>

PS C:\TACG> gcloud compute networks subnets update default --region=europe-west3 --enable-private-ip-google-access
Updated [https://www.googleapis.com/compute/v1/projects/tacg-gcp-XXXXXXXXXX/regions/europe-west3/subnetworks/default].

PS C:\TACG>

PS C:\TACG> gcloud compute networks subnets describe default --region=europe-west3 --format="get(privateIpGoogleAccess)"
True


Using gcloud to determine all used internal IPs:

PS C:\TACG> gcloud asset search-all-resources --asset-types='compute.googleapis.com/Instance' --query="networks/default" --format=json | jq ".[].additionalAttributes.internalIPs"
[
  "10.156.0.2"
]
PS C:\TACG>


As you must be aware of any default quotas that might break the deployment of the Worker VMs, you can get the quota information by using gcloud:

PS C:\TACG> gcloud compute project-info describe --project tacg-gcp-XXXXXXXXXX
commonInstanceMetadata:
  fingerprint: mhAFevpXe_g=
  items:
  - key: serial-port-enable
    value: 'TRUE'
  kind: compute#metadata
creationTimestamp: '2023-12-01T04:09:39.536-08:00'
defaultNetworkTier: PREMIUM
defaultServiceAccount: XXXXXXXXXXXX-compute@developer.gserviceaccount.com
id: '826031XXXXXXXXXXXX'
kind: compute#project
name: tacg-gcp-XXXXXXXXXX
quotas:
- limit: 1000.0
  metric: SNAPSHOTS
  usage: 0.0
- limit: 5.0
  metric: NETWORKS
  usage: 1.0
- limit: 100.0
  metric: FIREWALLS
  usage: 8.0
- limit: 100.0
  metric: IMAGES
  usage: 0.0
- limit: 8.0
  metric: STATIC_ADDRESSES
  usage: 0.0
- limit: 200.0
  metric: ROUTES
  usage: 0.0
- limit: 15.0
  metric: FORWARDING_RULES
  usage: 0.0
- limit: 50.0
  metric: TARGET_POOLS
  usage: 0.0
- limit: 75.0
  metric: HEALTH_CHECKS
  usage: 0.0
- limit: 8.0
  metric: IN_USE_ADDRESSES
  usage: 0.0
- limit: 50.0
  metric: TARGET_INSTANCES
  usage: 0.0
- limit: 10.0
  metric: TARGET_HTTP_PROXIES
  usage: 0.0
- limit: 10.0
  metric: URL_MAPS
  usage: 0.0
- limit: 50.0
  metric: BACKEND_SERVICES
  usage: 0.0
- limit: 100.0
  metric: INSTANCE_TEMPLATES
  usage: 2.0
- limit: 5.0
  metric: TARGET_VPN_GATEWAYS
  usage: 0.0
- limit: 10.0
  metric: VPN_TUNNELS
  usage: 0.0
- limit: 3.0
  metric: BACKEND_BUCKETS
  usage: 0.0
- limit: 10.0
  metric: ROUTERS
  usage: 0.0
- limit: 10.0
  metric: TARGET_SSL_PROXIES
  usage: 0.0
- limit: 10.0
  metric: TARGET_HTTPS_PROXIES
  usage: 0.0
- limit: 10.0
  metric: SSL_CERTIFICATES
  usage: 0.0
- limit: 100.0
  metric: SUBNETWORKS
  usage: 0.0
- limit: 10.0
  metric: TARGET_TCP_PROXIES
  usage: 0.0
- limit: 32.0
  metric: CPUS_ALL_REGIONS
  usage: 0.0
- limit: 10.0
  metric: SECURITY_POLICIES
  usage: 0.0
- limit: 100.0
  metric: SECURITY_POLICY_RULES
  usage: 0.0
- limit: 1000.0
  metric: XPN_SERVICE_PROJECTS
  usage: 0.0
- limit: 20.0
  metric: PACKET_MIRRORINGS
  usage: 0.0
- limit: 100.0
  metric: NETWORK_ENDPOINT_GROUPS
  usage: 0.0
- limit: 6.0
  metric: INTERCONNECTS
  usage: 0.0
- limit: 5000.0
  metric: GLOBAL_INTERNAL_ADDRESSES
  usage: 0.0
- limit: 5.0
  metric: VPN_GATEWAYS
  usage: 0.0
- limit: 100.0
  metric: MACHINE_IMAGES
  usage: 0.0
- limit: 20.0
  metric: SECURITY_POLICY_CEVAL_RULES
  usage: 0.0
- limit: 0.0
  metric: GPUS_ALL_REGIONS
  usage: 0.0
- limit: 5.0
  metric: EXTERNAL_VPN_GATEWAYS
  usage: 0.0
- limit: 1.0
  metric: PUBLIC_ADVERTISED_PREFIXES
  usage: 0.0
- limit: 10.0
  metric: PUBLIC_DELEGATED_PREFIXES
  usage: 0.0
- limit: 128.0
  metric: STATIC_BYOIP_ADDRESSES
  usage: 0.0
- limit: 10.0
  metric: NETWORK_FIREWALL_POLICIES
  usage: 0.0
- limit: 15.0
  metric: INTERNAL_TRAFFIC_DIRECTOR_FORWARDING_RULES
  usage: 0.0
- limit: 15.0
  metric: GLOBAL_EXTERNAL_MANAGED_FORWARDING_RULES
  usage: 0.0
- limit: 50.0
  metric: GLOBAL_INTERNAL_MANAGED_BACKEND_SERVICES
  usage: 0.0
- limit: 50.0
  metric: GLOBAL_EXTERNAL_MANAGED_BACKEND_SERVICES
  usage: 0.0
- limit: 50.0
  metric: GLOBAL_EXTERNAL_PROXY_LB_BACKEND_SERVICES
  usage: 0.0
- limit: 250.0
  metric: GLOBAL_INTERNAL_TRAFFIC_DIRECTOR_BACKEND_SERVICES
  usage: 0.0
selfLink: https://www.googleapis.com/compute/v1/projects/tacg-gcp-XXXXXXXXXX
vmDnsSetting: ZONAL_ONLY
xpnProjectStatus: UNSPECIFIED_XPN_PROJECT_STATUS

PS C:\TACG>

 

You can filter the Quotas or change them using the corresponding Quota pages on the Google Cloud console:

Google Cloud Console - Quotas

gcp-quota.png

gcp-quota1.png

gcp-quota-region.png


Getting the available Compute Images from Google Cloud Platform (GCP)

We want to automatically deploy the virtual machines necessary for the Domain Controller and the Cloud Connectors - so we need detailed configuration settings:

Get the available Compute images:

PS C:\TACG> gcloud compute images list --filter 'family ~ windows'
NAME                                   PROJECT        FAMILY             DEPRECATED  STATUS
windows-server-2016-dc-core-v20240313  windows-cloud  windows-2016-core              READY
windows-server-2016-dc-v20240313       windows-cloud  windows-2016                   READY
windows-server-2019-dc-core-v20240313  windows-cloud  windows-2019-core              READY
windows-server-2019-dc-v20240313       windows-cloud  windows-2019                   READY
windows-server-2022-dc-core-v20240313  windows-cloud  windows-2022-core              READY
windows-server-2022-dc-v20240313       windows-cloud  windows-2022                   READY

PS C:\TACG>

Note the value of the NAME you want to use - for example windows-server-2022-dc-v20240313.

Getting the available VM sizes from Google Cloud Platform (GCP)

We need to determine the available VM sizes. A gcloud call helps us to list the available instance types on Google Cloud Platform (GCP):

PS C:\TACG> gcloud compute machine-types list --filter="zone:( europe-west3-c )"
NAME              ZONE            CPUS  MEMORY_GB  DEPRECATED
c2-standard-16    europe-west3-c  16    64.00
c2-standard-30    europe-west3-c  30    120.00
c2-standard-4     europe-west3-c  4     16.00
c2-standard-60    europe-west3-c  60    240.00
c2-standard-8     europe-west3-c  8     32.00
c2d-highcpu-112   europe-west3-c  112   224.00
c2d-highcpu-16    europe-west3-c  16    32.00
c2d-highcpu-2     europe-west3-c  2     4.00
c2d-highcpu-32    europe-west3-c  32    64.00
c2d-highcpu-4     europe-west3-c  4     8.00
c2d-highcpu-56    europe-west3-c  56    112.00
c2d-highcpu-8     europe-west3-c  8     16.00
c2d-highmem-112   europe-west3-c  112   896.00
c2d-highmem-16    europe-west3-c  16    128.00
c2d-highmem-2     europe-west3-c  2     16.00
c2d-highmem-32    europe-west3-c  32    256.00
c2d-highmem-4     europe-west3-c  4     32.00
c2d-highmem-56    europe-west3-c  56    448.00
c2d-highmem-8     europe-west3-c  8     64.00
c2d-standard-112  europe-west3-c  112   448.00
c2d-standard-16   europe-west3-c  16    64.00
c2d-standard-2    europe-west3-c  2     8.00
c2d-standard-32   europe-west3-c  32    128.00
c2d-standard-4    europe-west3-c  4     16.00
c2d-standard-56   europe-west3-c  56    224.00
c2d-standard-8    europe-west3-c  8     32.00
e2-highcpu-16     europe-west3-c  16    16.00
e2-highcpu-2      europe-west3-c  2     2.00
e2-highcpu-32     europe-west3-c  32    32.00
e2-highcpu-4      europe-west3-c  4     4.00
e2-highcpu-8      europe-west3-c  8     8.00
e2-highmem-16     europe-west3-c  16    128.00
e2-highmem-2      europe-west3-c  2     16.00
e2-highmem-4      europe-west3-c  4     32.00
e2-highmem-8      europe-west3-c  8     64.00
e2-medium         europe-west3-c  2     4.00
e2-micro          europe-west3-c  2     1.00
e2-small          europe-west3-c  2     2.00
e2-standard-16    europe-west3-c  16    64.00
e2-standard-2     europe-west3-c  2     8.00
e2-standard-32    europe-west3-c  32    128.00
e2-standard-4     europe-west3-c  4     16.00
e2-standard-8     europe-west3-c  8     32.00
f1-micro          europe-west3-c  1     0.60
g1-small          europe-west3-c  1     1.70
m1-megamem-96     europe-west3-c  96    1433.60
m1-ultramem-160   europe-west3-c  160   3844.00
m1-ultramem-40    europe-west3-c  40    961.00
m1-ultramem-80    europe-west3-c  80    1922.00
n1-highcpu-16     europe-west3-c  16    14.40
n1-highcpu-2      europe-west3-c  2     1.80
n1-highcpu-32     europe-west3-c  32    28.80
n1-highcpu-4      europe-west3-c  4     3.60
n1-highcpu-64     europe-west3-c  64    57.60
n1-highcpu-8      europe-west3-c  8     7.20
n1-highcpu-96     europe-west3-c  96    86.40
n1-highmem-16     europe-west3-c  16    104.00
n1-highmem-2      europe-west3-c  2     13.00
n1-highmem-32     europe-west3-c  32    208.00
n1-highmem-4      europe-west3-c  4     26.00
n1-highmem-64     europe-west3-c  64    416.00
n1-highmem-8      europe-west3-c  8     52.00
n1-highmem-96     europe-west3-c  96    624.00
n1-megamem-96     europe-west3-c  96    1433.60    DEPRECATED
n1-standard-1     europe-west3-c  1     3.75
n1-standard-16    europe-west3-c  16    60.00
n1-standard-2     europe-west3-c  2     7.50
n1-standard-32    europe-west3-c  32    120.00
n1-standard-4     europe-west3-c  4     15.00
n1-standard-64    europe-west3-c  64    240.00
n1-standard-8     europe-west3-c  8     30.00
n1-standard-96    europe-west3-c  96    360.00
n1-ultramem-160   europe-west3-c  160   3844.00    DEPRECATED
n1-ultramem-40    europe-west3-c  40    961.00     DEPRECATED
n1-ultramem-80    europe-west3-c  80    1922.00    DEPRECATED
n2-highcpu-16     europe-west3-c  16    16.00
n2-highcpu-2      europe-west3-c  2     2.00
n2-highcpu-32     europe-west3-c  32    32.00
n2-highcpu-4      europe-west3-c  4     4.00
n2-highcpu-48     europe-west3-c  48    48.00
n2-highcpu-64     europe-west3-c  64    64.00
n2-highcpu-8      europe-west3-c  8     8.00
n2-highcpu-80     europe-west3-c  80    80.00
n2-highcpu-96     europe-west3-c  96    96.00
n2-highmem-128    europe-west3-c  128   864.00
n2-highmem-16     europe-west3-c  16    128.00
n2-highmem-2      europe-west3-c  2     16.00
n2-highmem-32     europe-west3-c  32    256.00
n2-highmem-4      europe-west3-c  4     32.00
n2-highmem-48     europe-west3-c  48    384.00
n2-highmem-64     europe-west3-c  64    512.00
n2-highmem-8      europe-west3-c  8     64.00
n2-highmem-80     europe-west3-c  80    640.00
n2-highmem-96     europe-west3-c  96    768.00
n2-standard-128   europe-west3-c  128   512.00
n2-standard-16    europe-west3-c  16    64.00
n2-standard-2     europe-west3-c  2     8.00
n2-standard-32    europe-west3-c  32    128.00
n2-standard-4     europe-west3-c  4     16.00
n2-standard-48    europe-west3-c  48    192.00
n2-standard-64    europe-west3-c  64    256.00
n2-standard-8     europe-west3-c  8     32.00
n2-standard-80    europe-west3-c  80    320.00
n2-standard-96    europe-west3-c  96    384.00
n2d-highcpu-128   europe-west3-c  128   128.00
n2d-highcpu-16    europe-west3-c  16    16.00
n2d-highcpu-2     europe-west3-c  2     2.00
n2d-highcpu-224   europe-west3-c  224   224.00
n2d-highcpu-32    europe-west3-c  32    32.00
n2d-highcpu-4     europe-west3-c  4     4.00
n2d-highcpu-48    europe-west3-c  48    48.00
n2d-highcpu-64    europe-west3-c  64    64.00
n2d-highcpu-8     europe-west3-c  8     8.00
n2d-highcpu-80    europe-west3-c  80    80.00
n2d-highcpu-96    europe-west3-c  96    96.00
n2d-highmem-16    europe-west3-c  16    128.00
n2d-highmem-2     europe-west3-c  2     16.00
n2d-highmem-32    europe-west3-c  32    256.00
n2d-highmem-4     europe-west3-c  4     32.00
n2d-highmem-48    europe-west3-c  48    384.00
n2d-highmem-64    europe-west3-c  64    512.00
n2d-highmem-8     europe-west3-c  8     64.00
n2d-highmem-80    europe-west3-c  80    640.00
n2d-highmem-96    europe-west3-c  96    768.00
n2d-standard-128  europe-west3-c  128   512.00
n2d-standard-16   europe-west3-c  16    64.00
n2d-standard-2    europe-west3-c  2     8.00
n2d-standard-224  europe-west3-c  224   896.00
n2d-standard-32   europe-west3-c  32    128.00
n2d-standard-4    europe-west3-c  4     16.00
n2d-standard-48   europe-west3-c  48    192.00
n2d-standard-64   europe-west3-c  64    256.00
n2d-standard-8    europe-west3-c  8     32.00
n2d-standard-80   europe-west3-c  80    320.00
n2d-standard-96   europe-west3-c  96    384.00
t2d-standard-1    europe-west3-c  1     4.00
t2d-standard-16   europe-west3-c  16    64.00
t2d-standard-2    europe-west3-c  2     8.00
t2d-standard-32   europe-west3-c  32    128.00
t2d-standard-4    europe-west3-c  4     16.00
t2d-standard-48   europe-west3-c  48    192.00
t2d-standard-60   europe-west3-c  60    240.00
t2d-standard-8    europe-west3-c  8     32.00

PS C:\TACG>

We need to filter the results to narrow down usable instances for the Cloud Connectors and the Admin-VM - we want to use instances with max. 2 vCPUs:

Filtered for CCs and Admin-VM:
PS C:\TACG> gcloud compute machine-types list --filter="(CPUS<=2) AND (zone:europe-west3-c)"
NAME            ZONE            CPUS  MEMORY_GB  DEPRECATED
c2d-highcpu-2   europe-west3-c  2     4.00
c2d-highmem-2   europe-west3-c  2     16.00
c2d-standard-2  europe-west3-c  2     8.00
e2-highcpu-2    europe-west3-c  2     2.00
e2-highmem-2    europe-west3-c  2     16.00
e2-medium       europe-west3-c  2     4.00
e2-micro        europe-west3-c  2     1.00
e2-small        europe-west3-c  2     2.00
e2-standard-2   europe-west3-c  2     8.00
f1-micro        europe-west3-c  1     0.60
g1-small        europe-west3-c  1     1.70
n1-highcpu-2    europe-west3-c  2     1.80
n1-highmem-2    europe-west3-c  2     13.00
n1-standard-1   europe-west3-c  1     3.75
n1-standard-2   europe-west3-c  2     7.50
n2-highcpu-2    europe-west3-c  2     2.00
n2-highmem-2    europe-west3-c  2     16.00
n2-standard-2   europe-west3-c  2     8.00
n2d-highcpu-2   europe-west3-c  2     2.00
n2d-highmem-2   europe-west3-c  2     16.00
n2d-standard-2  europe-west3-c  2     8.00
t2d-standard-1  europe-west3-c  1     4.00
t2d-standard-2  europe-west3-c  2     8.00

PS C:\TACG>

 

We need to filter the results to narrow down usable instances for the Worker VMs - we want to use instances with max. 8 vCPUs:
 
Filtered for Worker:
PS C:\TACG> gcloud compute machine-types list --filter="(CPUS<=8) AND (zone:europe-west3-c)"
NAME            ZONE            CPUS  MEMORY_GB  DEPRECATED
c2-standard-4   europe-west3-c  4     16.00
c2-standard-8   europe-west3-c  8     32.00
c2d-highcpu-2   europe-west3-c  2     4.00
c2d-highcpu-4   europe-west3-c  4     8.00
c2d-highcpu-8   europe-west3-c  8     16.00
c2d-highmem-2   europe-west3-c  2     16.00
c2d-highmem-4   europe-west3-c  4     32.00
c2d-highmem-8   europe-west3-c  8     64.00
c2d-standard-2  europe-west3-c  2     8.00
c2d-standard-4  europe-west3-c  4     16.00
c2d-standard-8  europe-west3-c  8     32.00
e2-highcpu-2    europe-west3-c  2     2.00
e2-highcpu-4    europe-west3-c  4     4.00
e2-highcpu-8    europe-west3-c  8     8.00
e2-highmem-2    europe-west3-c  2     16.00
e2-highmem-4    europe-west3-c  4     32.00
e2-highmem-8    europe-west3-c  8     64.00
e2-medium       europe-west3-c  2     4.00
e2-micro        europe-west3-c  2     1.00
e2-small        europe-west3-c  2     2.00
e2-standard-2   europe-west3-c  2     8.00
e2-standard-4   europe-west3-c  4     16.00
e2-standard-8   europe-west3-c  8     32.00
f1-micro        europe-west3-c  1     0.60
g1-small        europe-west3-c  1     1.70
n1-highcpu-2    europe-west3-c  2     1.80
n1-highcpu-4    europe-west3-c  4     3.60
n1-highcpu-8    europe-west3-c  8     7.20
n1-highmem-2    europe-west3-c  2     13.00
n1-highmem-4    europe-west3-c  4     26.00
n1-highmem-8    europe-west3-c  8     52.00
n1-standard-1   europe-west3-c  1     3.75
n1-standard-2   europe-west3-c  2     7.50
n1-standard-4   europe-west3-c  4     15.00
n1-standard-8   europe-west3-c  8     30.00
n2-highcpu-2    europe-west3-c  2     2.00
n2-highcpu-4    europe-west3-c  4     4.00
n2-highcpu-8    europe-west3-c  8     8.00
n2-highmem-2    europe-west3-c  2     16.00
n2-highmem-4    europe-west3-c  4     32.00
n2-highmem-8    europe-west3-c  8     64.00
n2-standard-2   europe-west3-c  2     8.00
n2-standard-4   europe-west3-c  4     16.00
n2-standard-8   europe-west3-c  8     32.00
n2d-highcpu-2   europe-west3-c  2     2.00
n2d-highcpu-4   europe-west3-c  4     4.00
n2d-highcpu-8   europe-west3-c  8     8.00
n2d-highmem-2   europe-west3-c  2     16.00
n2d-highmem-4   europe-west3-c  4     32.00
n2d-highmem-8   europe-west3-c  8     64.00
n2d-standard-2  europe-west3-c  2     8.00
n2d-standard-4  europe-west3-c  4     16.00
n2d-standard-8  europe-west3-c  8     32.00
t2d-standard-1  europe-west3-c  1     4.00
t2d-standard-2  europe-west3-c  2     8.00
t2d-standard-4  europe-west3-c  4     16.00
t2d-standard-8  europe-west3-c  8     32.00

PS C:\TACG>

Note the Name parameter of the Google Cloud Platform (GCP) instances you want to use.

Caution: Be sure that your subscription has no quota limitation on the chosen VM type and you have enough resources on Google Cloud Platform (GCP) to create all the Virtual Machines planned to put into the Machine Catalog by checking quotas - otherwise the creation of the Machine Catalog fails if there are not enough compute resources available!

Creating a Service Account for Google Cloud Platform (GCP) Authentication or checking the rights of an existing Service Account

Google Identity and Access Management (IAM) grants you granular access to specific Google Cloud resources.
It is important to define WHO has access to WHICH resources and WHAT they can do with them. Service accounts live inside projects, similar to other resources you deploy on Google Cloud.

gcp-create-service-account6.png

A Google Cloud Build Service Account is provisioned automatically once the Google APIs are enabled. The Cloud Build service account is identifiable with an email address that begins with the Project Number. The Cloud Build Account requires the following three roles:

  • Cloud Build Service Account (assigned by default)
  • Compute Instance Admin
  • Service Account User

To enable Citrix Cloud to access all needed entities, you need to add the following roles:

  • Compute Admin
  • Storage Admin
  • Cloud Build Editor
  • Service Account User
  • Cloud Datastore User

You can find detailed information about assigning these roles in the Tech Zone article Getting Started with Citrix DaaS on Google Cloud.

Use another gcloud-call to check if the Service Account you plan to use is assigned to the project and has the required IAM roles:

PS C:\TACG> gcloud projects list --impersonate-service-account=XXXXXXXXXX@XXXXXXXXXX.iam.gserviceaccount.com
PROJECT_ID           NAME      PROJECT_NUMBER
tacg-gcp-XXXXXXXXXX  TACG-GCP  XXXXXXXXXXXX

PS C:\TACG>

PS C:\TACG> gcloud projects get-iam-policy tacg-gcp-XXXXXXXXXX --flatten="bindings[].members" --format='table(bindings.role)' --filter="bindings.members:XXXXXXXXXX@XXXXXXXXXX.iam.gserviceaccount.com"
ROLE
roles/compute.admin
roles/iam.securityAdmin
roles/iam.cloudbuild.builds.editor
roles/iam.storage.admin
roles/iam.serviceAccountUser 
roles/iam.datastore.user
...
roles/owner

PS C:\TACG>

Further Software Components for Configuration and Deployment

The Terraform deployment needs actual versions of the following software components:

These components are required during the workflow. The Terraform engine looks for these files. In this guide we anticipate that the necessary software can be downloaded from a Storage Repository - Terraform creates a Storage Bucket where all necessary software is uploaded to.
The URIs of the Storage Repository can be set in the corresponding variables:

  • For the Cloud Connector: "CC_Install_CWCURI":"https://storage.googleapis.com/XXXXXXXXXX/cwcconnector.exe"
  • For the Remote Powershell SDK: "CC_Install_RPoSHURI":"https://storage.googleapis.com/XXXXXXXXXX/CitrixPoshSdk.exe"

Creating a Secure Client in Citrix Cloud

The Secure Client in Citrix Cloud is the same as the Access Key in Google Cloud Platform (GCP). It is used for Authentication.
API clients in Citrix Cloud are always tied to one administrator and one customer and are not visible to other administrators. If you want to access more than one customer, you must create API clients within each customer.

API clients are automatically restricted to the rights of the 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.

Select the Identity and Access Management option from the menu to create an API client. If this option does not appear, you do not have adequate permissions to create an API client. Contact your administrator to get the required permissions.

  1. Open Identity and Access Management in WebStudio:
    cc-create-apiclient1.png

  2. Click API Access, Secure Clients and put a name in the textbox adjacent to the button Create Client. After entering a name. click Create Client:
    cc-create-apiclient2.png

  3. After the Secure Client is created, copy and write down the shown ID and Secret:
    cc-create-apiclient3.png

    The Secret is only visible during creation - after closing the window, you cannot get it anymore.
    The client-id and client-secret fields are needed by the Citrix Terraform provider.
    The also needed customer-id field can be found in your Citrix Cloud details.

    Put the values in the corresponding .auto.tvars.json file:

{
        ...
        "CC_APIKey-ClientID":"f4xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxc15",
        "CC_APIKey-ClientSecret":"VxxxxxxxxxxxxxxxxxxA==",
        "CC_CustomerID": "uxxxxxxxxxxxxx",
        
"CC_APIKey_Type": "client_credentials",
        ...
}

Creating a Bearer Token in Citrix Cloud

The Bearer Token is needed to authorize some REST-API calls in Citrix Cloud. 
As the Citrix provider has not implemented all functionalities yet, some REST-API calls are still needed. The Bearer Token authorizes these calls.

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 is [https://api-us.cloud.com/cctrustoauth2/1234567890/tokens/clients]

In this example, we use the Postman application to create a Bearer Token:

  1. Paste the correct URI into Postman´s address bar and select POST as the method. Verify the correct settings of the API call.
    ![Creating a Bearer Token using Postman](/en-us/tech-zone/build/media/deployment-guides_citrix-daas-terraform-Google Cloud Platform (GCP) -create-bearertoken.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.

  2. Put the access-token value in the corresponding .auto.tvars.json file:

    {
            ...
            "CC_APIKey-ClientID":"f4xxxxxx-xxxx-xxxx-xxxx-Xxxxxxxxxc15",
            "CC_APIKey-ClientSecret":"VxxxxxxxxxxxxxxxxxxA==",
            "CC_CustomerID": "uxxxxxxxxxxxxx",
            
    "CC_APIKey_Type": "client_credentials",
            "CC_APIKey_Bearer": "CWSAuth bearer=eyJhbGciOiJSUzI1NiIsI...0q0IW7SZFVzeBittWnEwTYOZ7Q",
            ...
    }
     

 

NOTE: In this deployment, Terraform automatically requests a valid Bearer Token using a REST-API call from Citrix Cloud and stores the Bearer Token on the hard disk.
If subsequent needs for having a Bearer Token occur during script progress, Terraform loads the Bearer Token from the hard drive.

You also can use PowerShell to request a Bearer Token - therefore, you need a valid Secure Client stored in Citrix Cloud:

asnp Citrix*
$key= "f4eXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
$secret= "VJCXXXXXXXXXXXXXX"
$customer= "uXXXXXXXX"
$XDStoredCredentials = Set-XDCredentials -StoreAs default -ProfileType CloudApi -CustomerId $customer -APIKey $key -SecretKey $secret
$auth = Get-XDAuthentication
$BT = $GLOBAL:XDAuthToken | Out-File "<Path where to store the Token\BT.txt" -NoNewline -Encoding Ascii

 

Module 1: Create the initially needed Resources on Google Cloud Platform (GCP)

This module is split into the following configuration parts:

  • Creating the initially needed Resources on Google Cloud Platform (GCP):
    • Creating all needed Firewall rules on Google Cloud Platform (GCP)
    • Creating all needed internal IP addresses on Google Cloud Platform (GCP)
    • Creating all needed IAM policies on Google Cloud Platform (GCP)
    • Creating the Storage Bucket and uploading all required software items on Google Cloud Platform (GCP)
    • Creating a Windows Server 2022-based Master Image VM used for deploying the Machine Catalog in Step 3
    • Creating two Windows Server 2022-based VMs, which will be used as Cloud Connector VMs in Step 2
    • Creating a Windows Server 2022-based VM acting as an Administrative workstation for running Terraform steps 2 and 3—this is necessary because you will use WinRM for further configuration and deployment in steps 2 and 3!
    • Creating all necessary scripts for joining the VMs to the existing sub-domain
    • Putting the VMs into the existing sub-domain
    • Fetching and saving a valid Bearer Token

Terraform automatically does all these steps.

NOTE: This first part is separated from the next steps as we assume that there is no Admin-VM installed in the corresponding VPC/Subnet. The Admin-VM will be created now in Module 1 and all further modules must be run from it. Working with WinRM calls over the Internet would pose a security threat and would significantly reduce the performance of the Terraform-based deployment. These are the reasons for the split approach!

Please make sure you have configured the variables according to your needs.

The configuration can be started by following the normal Terraform workflow:
terraform init,
terraform plan
and if no errors occur

terraform apply

PS C:\TACG\_CCOnGCP\_CCOnGCP-Creation> terraform init

Initializing the backend...

Initializing provider plugins...
- Finding latest version of hashicorp/template...
- Finding hashicorp/google versions matching ">= 5.21.0"...
- Finding mastercard/restapi versions matching "1.18.2"...
- Finding citrix/citrix versions matching ">= 0.5.4"...
- Installing mastercard/restapi v1.18.2...
- Installed mastercard/restapi v1.18.2 (self-signed, key ID DCB8C431D71C30AB)
- Installing citrix/citrix v0.5.4...
- Installed citrix/citrix v0.5.4 (signed by a HashiCorp partner, key ID 25D62DD8407EA386)
- Installing hashicorp/template v2.2.0...
- Installed hashicorp/template v2.2.0 (signed by HashiCorp)
- Installing hashicorp/google v5.21.0...
- Installed hashicorp/google v5.21.0 (signed by HashiCorp)

Partner and community providers are signed by their developers.
If you'd like to know more about provider signing, you can read about it here:
https://www.terraform.io/docs/cli/plugins/signing.html

Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.

PS C:\TACG\_CCOnGCP\_CCOnGCP-Creation> terraform plan
data.template_file.Initial-Windows-BootScript-NoDC: Reading...
data.template_file.Initial-Windows-BootScript-NoDC: Read complete after 0s [id=d6adcc3a41a5c143a357d5555931b532da28a66cb82bfb58fea2801950cb2844]
data.google_compute_network.VPC: Reading...
data.google_compute_subnetwork.Subnet: Reading...
data.google_compute_subnetwork.Subnet: Read complete after 0s [id=projects/tacg-gcp-XXXXXXXXXX/regions/europe-west3/subnetworks/default]
data.google_compute_network.VPC: Read complete after 1s [id=projects/tacg-gcp-XXXXXXXXXX/global/networks/default]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the
following symbols:
  + create

Terraform will perform the following actions:

  # google_compute_address.internal-ip-adminvm will be created
  + resource "google_compute_address" "internal-ip-adminvm" {
      + address            = "10.156.0.7"
      + address_type       = "INTERNAL"
      + creation_timestamp = (known after apply)
      + effective_labels   = (known after apply)
      + id                 = (known after apply)
      + label_fingerprint  = (known after apply)
      + name               = "internal-ip-avm"
      + network_tier       = (known after apply)
      + prefix_length      = (known after apply)
      + project            = "tacg-gcp-XXXXXXXXXX"
      + purpose            = (known after apply)
      + region             = "europe-west3"
      + self_link          = (known after apply)
      + subnetwork         = "projects/tacg-gcp-XXXXXXXXXX/regions/europe-west3/subnetworks/default"
      + terraform_labels   = (known after apply)
      + users              = (known after apply)
    }

  # google_compute_address.internal-ip-cc1 will be created
  + resource "google_compute_address" "internal-ip-cc1" {
      + address            = "10.156.0.5"
      + address_type       = "INTERNAL"
      + creation_timestamp = (known after apply)
      + effective_labels   = (known after apply)
      + id                 = (known after apply)
      + label_fingerprint  = (known after apply)
      + name               = "internal-ip-cc1"
      + network_tier       = (known after apply)
      + prefix_length      = (known after apply)
      + project            = "tacg-gcp-XXXXXXXXXX"
      + purpose            = (known after apply)
      + region             = "europe-west3"
      + self_link          = (known after apply)
      + subnetwork         = "projects/tacg-gcp-XXXXXXXXXX/regions/europe-west3/subnetworks/default"
      + terraform_labels   = (known after apply)
      + users              = (known after apply)
    }

  # google_compute_address.internal-ip-cc2 will be created
  + resource "google_compute_address" "internal-ip-cc2" {
      + address            = "10.156.0.6"
      + address_type       = "INTERNAL"
      + creation_timestamp = (known after apply)
      + effective_labels   = (known after apply)
      + id                 = (known after apply)
      + label_fingerprint  = (known after apply)
      + name               = "internal-ip-cc2"
      + network_tier       = (known after apply)
      + prefix_length      = (known after apply)
      + project            = "tacg-gcp-XXXXXXXXXX"
      + purpose            = (known after apply)
      + region             = "europe-west3"
      + self_link          = (known after apply)
      + subnetwork         = "projects/tacg-gcp-XXXXXXXXXX/regions/europe-west3/subnetworks/default"
      + terraform_labels   = (known after apply)
      + users              = (known after apply)
    }

  # google_compute_address.internal-ip-wmi will be created
  + resource "google_compute_address" "internal-ip-wmi" {
      + address            = "10.156.0.8"
      + address_type       = "INTERNAL"
      + creation_timestamp = (known after apply)
      + effective_labels   = (known after apply)
      + id                 = (known after apply)
      + label_fingerprint  = (known after apply)
      + name               = "internal-ip-wmi"
      + network_tier       = (known after apply)
      + prefix_length      = (known after apply)
      + project            = "tacg-gcp-XXXXXXXXXX"
      + purpose            = (known after apply)
      + region             = "europe-west3"
      + self_link          = (known after apply)
      + subnetwork         = "projects/tacg-gcp-XXXXXXXXXX/regions/europe-west3/subnetworks/default"
      + terraform_labels   = (known after apply)
      + users              = (known after apply)
    }

  # google_compute_firewall.Allow-All-ICMP will be created
  + resource "google_compute_firewall" "Allow-All-ICMP" {
      + creation_timestamp = (known after apply)
      + destination_ranges = (known after apply)
      + direction          = (known after apply)
      + enable_logging     = (known after apply)
      + id                 = (known after apply)
      + name               = "tacg-gcp-tf-fw-allow-all-icmp"
      + network            = "default"
      + priority           = 1000
      + project            = "tacg-gcp-XXXXXXXXXX"
      + self_link          = (known after apply)
      + source_ranges      = [
          + "10.156.0.0/20",
        ]
      + target_tags        = [
          + "all-icmp",
        ]

      + allow {
          + ports    = []
          + protocol = "icmp"
        }
    }

  # google_compute_firewall.Allow-All-TCP will be created
  + resource "google_compute_firewall" "Allow-All-TCP" {
      + creation_timestamp = (known after apply)
      + destination_ranges = (known after apply)
      + direction          = (known after apply)
      + enable_logging     = (known after apply)
      + id                 = (known after apply)
      + name               = "tacg-gcp-tf-fw-allow-all-tcp"
      + network            = "default"
      + priority           = 1000
      + project            = "tacg-gcp-XXXXXXXXXX"
      + self_link          = (known after apply)
      + source_ranges      = [
          + "10.156.0.0/20",
        ]
      + target_tags        = [
          + "all-tcp",
        ]

      + allow {
          + ports    = [
              + "0-65534",
            ]
          + protocol = "tcp"
        }
    }

  # google_compute_firewall.Allow-All-UDP will be created
  + resource "google_compute_firewall" "Allow-All-UDP" {
      + creation_timestamp = (known after apply)
      + destination_ranges = (known after apply)
      + direction          = (known after apply)
      + enable_logging     = (known after apply)
      + id                 = (known after apply)
      + name               = "tacg-gcp-tf-fw-allow-all-udp"
      + network            = "default"
      + priority           = 1000
      + project            = "tacg-gcp-XXXXXXXXXX"
      + self_link          = (known after apply)
      + source_ranges      = [
          + "10.156.0.0/20",
        ]
      + target_tags        = [
          + "all-udp",
        ]

      + allow {
          + ports    = [
              + "0-65534",
            ]
          + protocol = "udp"
        }
    }

  # google_compute_firewall.Allow-HTTP will be created
  + resource "google_compute_firewall" "Allow-HTTP" {
      + creation_timestamp = (known after apply)
      + destination_ranges = (known after apply)
      + direction          = (known after apply)
      + enable_logging     = (known after apply)
      + id                 = (known after apply)
      + name               = "tacg-gcp-tf-fw-allow-http"
      + network            = "default"
      + priority           = 1000
      + project            = "tacg-gcp-XXXXXXXXXX"
      + self_link          = (known after apply)
      + source_ranges      = [
          + "0.0.0.0/0",
        ]
      + target_tags        = [
          + "http",
        ]

      + allow {
          + ports    = [
              + "80",
            ]
          + protocol = "tcp"
        }
    }

  # google_compute_firewall.Allow-HTTPS will be created
  + resource "google_compute_firewall" "Allow-HTTPS" {
      + creation_timestamp = (known after apply)
      + destination_ranges = (known after apply)
      + direction          = (known after apply)
      + enable_logging     = (known after apply)
      + id                 = (known after apply)
      + name               = "tacg-gcp-tf-fw-allow-https"
      + network            = "default"
      + priority           = 1000
      + project            = "tacg-gcp-XXXXXXXXXX"
      + self_link          = (known after apply)
      + source_ranges      = [
          + "0.0.0.0/0",
        ]
      + target_tags        = [
          + "https",
        ]

      + allow {
          + ports    = [
              + "443",
            ]
          + protocol = "tcp"
        }
    }

  # google_compute_firewall.Allow-RDP will be created
  + resource "google_compute_firewall" "Allow-RDP" {
      + creation_timestamp = (known after apply)
      + destination_ranges = (known after apply)
      + direction          = (known after apply)
      + enable_logging     = (known after apply)
      + id                 = (known after apply)
      + name               = "tacg-gcp-tf-fw-allow-rdp"
      + network            = "default"
      + priority           = 1000
      + project            = "tacg-gcp-XXXXXXXXXX"
      + self_link          = (known after apply)
      + source_ranges      = [
          + "0.0.0.0/0",
        ]
      + target_tags        = [
          + "rdp",
        ]

      + allow {
          + ports    = [
              + "3389",
            ]
          + protocol = "tcp"
        }
    }

  # google_compute_firewall.Allow-SSH will be created
  + resource "google_compute_firewall" "Allow-SSH" {
      + creation_timestamp = (known after apply)
      + destination_ranges = (known after apply)
      + direction          = (known after apply)
      + enable_logging     = (known after apply)
      + id                 = (known after apply)
      + name               = "tacg-gcp-tf-fw-allow-ssh"
      + network            = "default"
      + priority           = 1000
      + project            = "tacg-gcp-XXXXXXXXXX"
      + self_link          = (known after apply)
      + source_ranges      = [
          + "10.156.0.0/20",
        ]
      + target_tags        = [
          + "ssh",
        ]

      + allow {
          + ports    = [
              + "22",
            ]
          + protocol = "tcp"
        }
    }

  # google_compute_firewall.Allow-WinRM will be created
  + resource "google_compute_firewall" "Allow-WinRM" {
      + creation_timestamp = (known after apply)
      + destination_ranges = (known after apply)
      + direction          = (known after apply)
      + enable_logging     = (known after apply)
      + id                 = (known after apply)
      + name               = "tacg-gcp-tf-fw-allow-winrm"
      + network            = "default"
      + priority           = 1000
      + project            = "tacg-gcp-XXXXXXXXXX"
      + self_link          = (known after apply)
      + source_ranges      = [
          + "10.156.0.0/20",
        ]
      + target_tags        = [
          + "winrm",
        ]

      + allow {
          + ports    = [
              + "5985",
              + "5986",
            ]
          + protocol = "tcp"
        }
    }

  # google_compute_instance.AdminVM will be created
  + resource "google_compute_instance" "AdminVM" {
      + can_ip_forward       = false
      + cpu_platform         = (known after apply)
      + current_status       = (known after apply)
      + deletion_protection  = false
      + effective_labels     = (known after apply)
      + guest_accelerator    = (known after apply)
      + hostname             = "tacg-gcp-tf-avm.gcp.the-austrian-citrix-guy.at"
      + id                   = (known after apply)
      + instance_id          = (known after apply)
      + label_fingerprint    = (known after apply)
      + machine_type         = "n2-standard-2"
      + metadata             = {
          + "sysprep-specialize-script-ps1" = <<-EOT
                Start-Sleep -Seconds 60
                #Set DNS Server address
                Set-DNSClientServerAddress -ServerAddresses '10.156.0.2' -InterfaceIndex (Get-NetAdapter).InterfaceIndex

                netdom.exe join $env:COMPUTERNAME /domain:gcp.the-austrian-citrix-guy.at /UserD:XXXXXXXXXX@gcp.the-austrian-citrix-guy.at /PasswordD:XXXXXXXXXXXX /reboot:5
            EOT
        }
      + metadata_fingerprint = (known after apply)
      + min_cpu_platform     = (known after apply)
      + name                 = "tacg-gcp-tf-avm"
      + project              = "tacg-gcp-XXXXXXXXXX"
      + self_link            = (known after apply)
      + tags                 = [
          + "all-icmp",
          + "all-tcp",
          + "all-udp",
          + "http",
          + "https",
          + "rdp",
          + "winrm",
        ]
      + tags_fingerprint     = (known after apply)
      + terraform_labels     = (known after apply)
      + zone                 = "europe-west3-c"

      + boot_disk {
          + auto_delete                = true
          + device_name                = (known after apply)
          + disk_encryption_key_sha256 = (known after apply)
          + kms_key_self_link          = (known after apply)
          + mode                       = "READ_WRITE"
          + source                     = (known after apply)

          + initialize_params {
              + image                  = "windows-server-2022-dc-v20240313"
              + labels                 = (known after apply)
              + provisioned_iops       = (known after apply)
              + provisioned_throughput = (known after apply)
              + size                   = (known after apply)
              + type                   = (known after apply)
            }
        }

      + network_interface {
          + internal_ipv6_prefix_length = (known after apply)
          + ipv6_access_type            = (known after apply)
          + ipv6_address                = (known after apply)
          + name                        = (known after apply)
          + network                     = "default"
          + network_ip                  = "10.156.0.7"
          + stack_type                  = (known after apply)
          + subnetwork                  = "default"
          + subnetwork_project          = (known after apply)

          + access_config {
              + nat_ip       = (known after apply)
              + network_tier = (known after apply)
            }
        }
    }

  # google_compute_instance.CC1 will be created
  + resource "google_compute_instance" "CC1" {
      + can_ip_forward       = false
      + cpu_platform         = (known after apply)
      + current_status       = (known after apply)
      + deletion_protection  = false
      + effective_labels     = (known after apply)
      + guest_accelerator    = (known after apply)
      + hostname             = "tacg-gcp-tf-cc1.gcp.the-austrian-citrix-guy.at"
      + id                   = (known after apply)
      + instance_id          = (known after apply)
      + label_fingerprint    = (known after apply)
      + machine_type         = "n2-standard-2"
      + metadata             = {
          + "sysprep-specialize-script-ps1" = <<-EOT
                Start-Sleep -Seconds 60
                #Set DNS Server address
                Set-DNSClientServerAddress -ServerAddresses '10.156.0.2' -InterfaceIndex (Get-NetAdapter).InterfaceIndex

                netdom.exe join $env:COMPUTERNAME /domain:gcp.the-austrian-citrix-guy.at /UserD:XXXXXXXXXX@gcp.the-austrian-citrix-guy.at /PasswordD:XXXXXXXXXXXX /reboot:5
            EOT
        }
      + metadata_fingerprint = (known after apply)
      + min_cpu_platform     = (known after apply)
      + name                 = "tacg-gcp-tf-cc1"
      + project              = "tacg-gcp-XXXXXXXXXX"
      + self_link            = (known after apply)
      + tags                 = [
          + "all-icmp",
          + "all-tcp",
          + "all-udp",
          + "http",
          + "https",
          + "rdp",
          + "winrm",
        ]
      + tags_fingerprint     = (known after apply)
      + terraform_labels     = (known after apply)
      + zone                 = "europe-west3-c"

      + boot_disk {
          + auto_delete                = true
          + device_name                = (known after apply)
          + disk_encryption_key_sha256 = (known after apply)
          + kms_key_self_link          = (known after apply)
          + mode                       = "READ_WRITE"
          + source                     = (known after apply)

          + initialize_params {
              + image                  = "windows-server-2022-dc-v20240313"
              + labels                 = (known after apply)
              + provisioned_iops       = (known after apply)
              + provisioned_throughput = (known after apply)
              + size                   = (known after apply)
              + type                   = (known after apply)
            }
        }

      + network_interface {
          + internal_ipv6_prefix_length = (known after apply)
          + ipv6_access_type            = (known after apply)
          + ipv6_address                = (known after apply)
          + name                        = (known after apply)
          + network                     = "default"
          + network_ip                  = "10.156.0.5"
          + stack_type                  = (known after apply)
          + subnetwork                  = "default"
          + subnetwork_project          = (known after apply)

          + access_config {
              + nat_ip       = (known after apply)
              + network_tier = (known after apply)
            }
        }
    }

  # google_compute_instance.CC2 will be created
  + resource "google_compute_instance" "CC2" {
      + can_ip_forward       = false
      + cpu_platform         = (known after apply)
      + current_status       = (known after apply)
      + deletion_protection  = false
      + effective_labels     = (known after apply)
      + guest_accelerator    = (known after apply)
      + hostname             = "tacg-gcp-tf-cc2.gcp.the-austrian-citrix-guy.at"
      + id                   = (known after apply)
      + instance_id          = (known after apply)
      + label_fingerprint    = (known after apply)
      + machine_type         = "n2-standard-2"
      + metadata             = {
          + "sysprep-specialize-script-ps1" = <<-EOT
                Start-Sleep -Seconds 60
                #Set DNS Server address
                Set-DNSClientServerAddress -ServerAddresses '10.156.0.2' -InterfaceIndex (Get-NetAdapter).InterfaceIndex

                netdom.exe join $env:COMPUTERNAME /domain:gcp.the-austrian-citrix-guy.at /UserD:XXXXXXXXXX@gcp.the-austrian-citrix-guy.at /PasswordD:XXXXXXXXXXXX /reboot:5
            EOT
        }
      + metadata_fingerprint = (known after apply)
      + min_cpu_platform     = (known after apply)
      + name                 = "tacg-gcp-tf-cc2"
      + project              = "tacg-gcp-XXXXXXXXXX"
      + self_link            = (known after apply)
      + tags                 = [
          + "all-icmp",
          + "all-tcp",
          + "all-udp",
          + "http",
          + "https",
          + "rdp",
          + "winrm",
        ]
      + tags_fingerprint     = (known after apply)
      + terraform_labels     = (known after apply)
      + zone                 = "europe-west3-c"

      + boot_disk {
          + auto_delete                = true
          + device_name                = (known after apply)
          + disk_encryption_key_sha256 = (known after apply)
          + kms_key_self_link          = (known after apply)
          + mode                       = "READ_WRITE"
          + source                     = (known after apply)

          + initialize_params {
              + image                  = "windows-server-2022-dc-v20240313"
              + labels                 = (known after apply)
              + provisioned_iops       = (known after apply)
              + provisioned_throughput = (known after apply)
              + size                   = (known after apply)
              + type                   = (known after apply)
            }
        }

      + network_interface {
          + internal_ipv6_prefix_length = (known after apply)
          + ipv6_access_type            = (known after apply)
          + ipv6_address                = (known after apply)
          + name                        = (known after apply)
          + network                     = "default"
          + network_ip                  = "10.156.0.6"
          + stack_type                  = (known after apply)
          + subnetwork                  = "default"
          + subnetwork_project          = (known after apply)

          + access_config {
              + nat_ip       = (known after apply)
              + network_tier = (known after apply)
            }
        }
    }

  # google_compute_instance.WMI will be created
  + resource "google_compute_instance" "WMI" {
      + can_ip_forward       = false
      + cpu_platform         = (known after apply)
      + current_status       = (known after apply)
      + deletion_protection  = false
      + effective_labels     = (known after apply)
      + guest_accelerator    = (known after apply)
      + hostname             = "tacg-gcp-tf-wmi.gcp.the-austrian-citrix-guy.at"
      + id                   = (known after apply)
      + instance_id          = (known after apply)
      + label_fingerprint    = (known after apply)
      + machine_type         = "n2-standard-2"
      + metadata             = {
          + "sysprep-specialize-script-ps1" = <<-EOT
                Start-Sleep -Seconds 60
                #Set DNS Server address
                Set-DNSClientServerAddress -ServerAddresses '10.156.0.2' -InterfaceIndex (Get-NetAdapter).InterfaceIndex

                netdom.exe join $env:COMPUTERNAME /domain:gcp.the-austrian-citrix-guy.at /UserD:XXXXXXXXXX@gcp.the-austrian-citrix-guy.at /PasswordD:XXXXXXXXXXXX /reboot:5
            EOT
        }
      + metadata_fingerprint = (known after apply)
      + min_cpu_platform     = (known after apply)
      + name                 = "tacg-gcp-tf-wmi"
      + project              = "tacg-gcp-XXXXXXXXXX"
      + self_link            = (known after apply)
      + tags                 = [
          + "all-icmp",
          + "all-tcp",
          + "all-udp",
          + "http",
          + "https",
          + "rdp",
          + "winrm",
        ]
      + tags_fingerprint     = (known after apply)
      + terraform_labels     = (known after apply)
      + zone                 = "europe-west3-c"

      + boot_disk {
          + auto_delete                = true
          + device_name                = (known after apply)
          + disk_encryption_key_sha256 = (known after apply)
          + kms_key_self_link          = (known after apply)
          + mode                       = "READ_WRITE"
          + source                     = (known after apply)

          + initialize_params {
              + image                  = "windows-server-2022-dc-v20240313"
              + labels                 = (known after apply)
              + provisioned_iops       = (known after apply)
              + provisioned_throughput = (known after apply)
              + size                   = (known after apply)
              + type                   = (known after apply)
            }
        }

      + network_interface {
          + internal_ipv6_prefix_length = (known after apply)
          + ipv6_access_type            = (known after apply)
          + ipv6_address                = (known after apply)
          + name                        = (known after apply)
          + network                     = "default"
          + network_ip                  = "10.156.0.8"
          + stack_type                  = (known after apply)
          + subnetwork                  = "default"
          + subnetwork_project          = (known after apply)

          + access_config {
              + nat_ip       = (known after apply)
              + network_tier = (known after apply)
            }
        }
    }

Plan: 24 to add, 0 to change, 0 to destroy.

Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if
you run "terraform apply" now.

PS C:\TACG\_CCOnGCP\_CCOnGCP-Creation> terraform apply
data.template_file.Initial-Windows-BootScript-NoDC: Reading...
data.template_file.Initial-Windows-BootScript-NoDC: Read complete after 0s [id=d6adcc3a41a5c143a357d5555931b532da28a66cb82bfb58fea2801950cb2844]
data.google_compute_network.VPC: Reading...
data.google_compute_subnetwork.Subnet: Reading...
data.google_compute_subnetwork.Subnet: Read complete after 0s [id=projects/tacg-gcp-XXXXXXXXXX/regions/europe-west3/subnetworks/default]
data.google_compute_network.VPC: Read complete after 1s [id=projects/tacg-gcp-XXXXXXXXXX/global/networks/default]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the
following symbols:
  + create

Terraform will perform the following actions:

  # google_compute_address.internal-ip-adminvm will be created
  + resource "google_compute_address" "internal-ip-adminvm" {
      + address            = "10.156.0.7"
      + address_type       = "INTERNAL"
      + creation_timestamp = (known after apply)
      + effective_labels   = (known after apply)
      + id                 = (known after apply)
      + label_fingerprint  = (known after apply)
      + name               = "internal-ip-avm"
      + network_tier       = (known after apply)
      + prefix_length      = (known after apply)
      + project            = "tacg-gcp-XXXXXXXXXX"
      + purpose            = (known after apply)
      + region             = "europe-west3"
      + self_link          = (known after apply)
      + subnetwork         = "projects/tacg-gcp-XXXXXXXXXX/regions/europe-west3/subnetworks/default"
      + terraform_labels   = (known after apply)
      + users              = (known after apply)
    }

  # google_compute_address.internal-ip-cc1 will be created
  + resource "google_compute_address" "internal-ip-cc1" {
      + address            = "10.156.0.5"
      + address_type       = "INTERNAL"
      + creation_timestamp = (known after apply)
      + effective_labels   = (known after apply)
      + id                 = (known after apply)
      + label_fingerprint  = (known after apply)
      + name               = "internal-ip-cc1"
      + network_tier       = (known after apply)
      + prefix_length      = (known after apply)
      + project            = "tacg-gcp-XXXXXXXXXX"
      + purpose            = (known after apply)
      + region             = "europe-west3"
      + self_link          = (known after apply)
      + subnetwork         = "projects/tacg-gcp-XXXXXXXXXX/regions/europe-west3/subnetworks/default"
      + terraform_labels   = (known after apply)
      + users              = (known after apply)
    }

  # google_compute_address.internal-ip-cc2 will be created
  + resource "google_compute_address" "internal-ip-cc2" {
      + address            = "10.156.0.6"
      + address_type       = "INTERNAL"
      + creation_timestamp = (known after apply)
      + effective_labels   = (known after apply)
      + id                 = (known after apply)
      + label_fingerprint  = (known after apply)
      + name               = "internal-ip-cc2"
      + network_tier       = (known after apply)
      + prefix_length      = (known after apply)
      + project            = "tacg-gcp-XXXXXXXXXX"
      + purpose            = (known after apply)
      + region             = "europe-west3"
      + self_link          = (known after apply)
      + subnetwork         = "projects/tacg-gcp-XXXXXXXXXX/regions/europe-west3/subnetworks/default"
      + terraform_labels   = (known after apply)
      + users              = (known after apply)
    }

  # google_compute_address.internal-ip-wmi will be created
  + resource "google_compute_address" "internal-ip-wmi" {
      + address            = "10.156.0.8"
      + address_type       = "INTERNAL"
      + creation_timestamp = (known after apply)
      + effective_labels   = (known after apply)
      + id                 = (known after apply)
      + label_fingerprint  = (known after apply)
      + name               = "internal-ip-wmi"
      + network_tier       = (known after apply)
      + prefix_length      = (known after apply)
      + project            = "tacg-gcp-XXXXXXXXXX"
      + purpose            = (known after apply)
      + region             = "europe-west3"
      + self_link          = (known after apply)
      + subnetwork         = "projects/tacg-gcp-XXXXXXXXXX/regions/europe-west3/subnetworks/default"
      + terraform_labels   = (known after apply)
      + users              = (known after apply)
    }

  # google_compute_firewall.Allow-All-ICMP will be created
  + resource "google_compute_firewall" "Allow-All-ICMP" {
      + creation_timestamp = (known after apply)
      + destination_ranges = (known after apply)
      + direction          = (known after apply)
      + enable_logging     = (known after apply)
      + id                 = (known after apply)
      + name               = "tacg-gcp-tf-fw-allow-all-icmp"
      + network            = "default"
      + priority           = 1000
      + project            = "tacg-gcp-XXXXXXXXXX"
      + self_link          = (known after apply)
      + source_ranges      = [
          + "10.156.0.0/20",
        ]
      + target_tags        = [
          + "all-icmp",
        ]

      + allow {
          + ports    = []
          + protocol = "icmp"
        }
    }

  # google_compute_firewall.Allow-All-TCP will be created
  + resource "google_compute_firewall" "Allow-All-TCP" {
      + creation_timestamp = (known after apply)
      + destination_ranges = (known after apply)
      + direction          = (known after apply)
      + enable_logging     = (known after apply)
      + id                 = (known after apply)
      + name               = "tacg-gcp-tf-fw-allow-all-tcp"
      + network            = "default"
      + priority           = 1000
      + project            = "tacg-gcp-XXXXXXXXXX"
      + self_link          = (known after apply)
      + source_ranges      = [
          + "10.156.0.0/20",
        ]
      + target_tags        = [
          + "all-tcp",
        ]

      + allow {
          + ports    = [
              + "0-65534",
            ]
          + protocol = "tcp"
        }
    }

  # google_compute_firewall.Allow-All-UDP will be created
  + resource "google_compute_firewall" "Allow-All-UDP" {
      + creation_timestamp = (known after apply)
      + destination_ranges = (known after apply)
      + direction          = (known after apply)
      + enable_logging     = (known after apply)
      + id                 = (known after apply)
      + name               = "tacg-gcp-tf-fw-allow-all-udp"
      + network            = "default"
      + priority           = 1000
      + project            = "tacg-gcp-XXXXXXXXXX"
      + self_link          = (known after apply)
      + source_ranges      = [
          + "10.156.0.0/20",
        ]
      + target_tags        = [
          + "all-udp",
        ]

      + allow {
          + ports    = [
              + "0-65534",
            ]
          + protocol = "udp"
        }
    }

  # google_compute_firewall.Allow-HTTP will be created
  + resource "google_compute_firewall" "Allow-HTTP" {
      + creation_timestamp = (known after apply)
      + destination_ranges = (known after apply)
      + direction          = (known after apply)
      + enable_logging     = (known after apply)
      + id                 = (known after apply)
      + name               = "tacg-gcp-tf-fw-allow-http"
      + network            = "default"
      + priority           = 1000
      + project            = "tacg-gcp-XXXXXXXXXX"
      + self_link          = (known after apply)
      + source_ranges      = [
          + "0.0.0.0/0",
        ]
      + target_tags        = [
          + "http",
        ]

      + allow {
          + ports    = [
              + "80",
            ]
          + protocol = "tcp"
        }
    }

  # google_compute_firewall.Allow-HTTPS will be created
  + resource "google_compute_firewall" "Allow-HTTPS" {
      + creation_timestamp = (known after apply)
      + destination_ranges = (known after apply)
      + direction          = (known after apply)
      + enable_logging     = (known after apply)
      + id                 = (known after apply)
      + name               = "tacg-gcp-tf-fw-allow-https"
      + network            = "default"
      + priority           = 1000
      + project            = "tacg-gcp-XXXXXXXXXX"
      + self_link          = (known after apply)
      + source_ranges      = [
          + "0.0.0.0/0",
        ]
      + target_tags        = [
          + "https",
        ]

      + allow {
          + ports    = [
              + "443",
            ]
          + protocol = "tcp"
        }
    }

  # google_compute_firewall.Allow-RDP will be created
  + resource "google_compute_firewall" "Allow-RDP" {
      + creation_timestamp = (known after apply)
      + destination_ranges = (known after apply)
      + direction          = (known after apply)
      + enable_logging     = (known after apply)
      + id                 = (known after apply)
      + name               = "tacg-gcp-tf-fw-allow-rdp"
      + network            = "default"
      + priority           = 1000
      + project            = "tacg-gcp-XXXXXXXXXX"
      + self_link          = (known after apply)
      + source_ranges      = [
          + "0.0.0.0/0",
        ]
      + target_tags        = [
          + "rdp",
        ]

      + allow {
          + ports    = [
              + "3389",
            ]
          + protocol = "tcp"
        }
    }

  # google_compute_firewall.Allow-SSH will be created
  + resource "google_compute_firewall" "Allow-SSH" {
      + creation_timestamp = (known after apply)
      + destination_ranges = (known after apply)
      + direction          = (known after apply)
      + enable_logging     = (known after apply)
      + id                 = (known after apply)
      + name               = "tacg-gcp-tf-fw-allow-ssh"
      + network            = "default"
      + priority           = 1000
      + project            = "tacg-gcp-XXXXXXXXXX"
      + self_link          = (known after apply)
      + source_ranges      = [
          + "10.156.0.0/20",
        ]
      + target_tags        = [
          + "ssh",
        ]

      + allow {
          + ports    = [
              + "22",
            ]
          + protocol = "tcp"
        }
    }

  # google_compute_firewall.Allow-WinRM will be created
  + resource "google_compute_firewall" "Allow-WinRM" {
      + creation_timestamp = (known after apply)
      + destination_ranges = (known after apply)
      + direction          = (known after apply)
      + enable_logging     = (known after apply)
      + id                 = (known after apply)
      + name               = "tacg-gcp-tf-fw-allow-winrm"
      + network            = "default"
      + priority           = 1000
      + project            = "tacg-gcp-XXXXXXXXXX"
      + self_link          = (known after apply)
      + source_ranges      = [
          + "10.156.0.0/20",
        ]
      + target_tags        = [
          + "winrm",
        ]

      + allow {
          + ports    = [
              + "5985",
              + "5986",
            ]
          + protocol = "tcp"
        }
    }

  # google_storage_bucket.Prereqs will be created
  + resource "google_storage_bucket" "Prereqs" {
      + effective_labels            = (known after apply)
      + force_destroy               = true
      + id                          = (known after apply)
      + location                    = "EU"
      + name                        = "XXXXXXXXXX"
      + project                     = (known after apply)
      + public_access_prevention    = (known after apply)
      + rpo                         = (known after apply)
      + self_link                   = (known after apply)
      + storage_class               = "STANDARD"
      + terraform_labels            = (known after apply)
      + uniform_bucket_level_access = (known after apply)
      + url                         = (known after apply)
    }

  # google_storage_bucket_access_control.Prereqs will be created
  + resource "google_storage_bucket_access_control" "Prereqs" {
      + bucket = "XXXXXXXXXX"
      + domain = (known after apply)
      + email  = (known after apply)
      + entity = "allUsers"
      + id     = (known after apply)
      + role   = "READER"
    }

  # google_storage_bucket_iam_binding.iam will be created
  + resource "google_storage_bucket_iam_binding" "iam" {
      + bucket  = "XXXXXXXXXX"
      + etag    = (known after apply)
      + id      = (known after apply)
      + members = [
          + "allUsers",
        ]
      + role    = "roles/storage.objectViewer"
    }

  # google_storage_bucket_object.Prereqs will be created
  + resource "google_storage_bucket_object" "Prereqs" {
      + bucket         = "XXXXXXXXXX"
      + content        = (sensitive value)
      + content_type   = (known after apply)
      + crc32c         = (known after apply)
      + detect_md5hash = "different hash"
      + id             = (known after apply)
      + kms_key_name   = (known after apply)
      + md5hash        = (known after apply)
      + media_link     = (known after apply)
      + name           = "domain-join-script.ps1"
      + output_name    = (known after apply)
      + self_link      = (known after apply)
      + source         = "./TF-Domain-Join-Script.ps1"
      + storage_class  = (known after apply)
    }

Plan: 24 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

google_storage_bucket.Prereqs: Creating...
google_storage_bucket.Prereqs: Creation complete after 2s [id=XXXXXXXXXX]
google_storage_bucket_access_control.Prereqs: Creating...
google_storage_bucket_object.Prereqs: Creating...
google_storage_bucket_object.Prereqs: Creation complete after 1s [id=XXXXXXXXXX-domain-join-script.ps1]
google_storage_bucket_access_control.Prereqs: Creation complete after 2s [id=XXXXXXXXXX/allUsers]
google_compute_address.internal-ip-adminvm: Creating...
google_compute_firewall.Allow-All-UDP: Creating...
google_compute_firewall.Allow-SSH: Creating...
google_compute_firewall.Allow-HTTPS: Creating...
google_compute_firewall.Allow-All-TCP: Creating...
google_compute_firewall.Allow-HTTP: Creating...
google_compute_firewall.Allow-All-ICMP: Creating...
google_compute_firewall.Allow-WinRM: Creating...
google_compute_firewall.Allow-RDP: Creating...
google_compute_address.internal-ip-cc2: Creating...
google_compute_address.internal-ip-adminvm: Still creating... [10s elapsed]
google_compute_firewall.Allow-All-TCP: Still creating... [10s elapsed]
google_compute_firewall.Allow-All-ICMP: Still creating... [10s elapsed]
google_compute_firewall.Allow-SSH: Still creating... [10s elapsed]
google_compute_firewall.Allow-RDP: Still creating... [10s elapsed]
google_compute_firewall.Allow-All-UDP: Still creating... [10s elapsed]
google_compute_firewall.Allow-HTTPS: Still creating... [10s elapsed]
google_compute_firewall.Allow-WinRM: Still creating... [10s elapsed]
google_compute_firewall.Allow-HTTP: Still creating... [10s elapsed]
google_storage_bucket.Prereqs: Creating...
google_storage_bucket.Prereqs: Creation complete after 3s [id=XXXXXXXXXX]
google_storage_bucket_access_control.Prereqs: Creating...
google_storage_bucket_object.Prereqs: Creating...
google_storage_bucket_iam_binding.iam: Creating...
google_storage_bucket_object.Prereqs: Creation complete after 0s [id=XXXXXXXXXX-domain-join-script.ps1]
google_storage_bucket_access_control.Prereqs: Creation complete after 1s [id=XXXXXXXXXX/allUsers]
google_storage_bucket_iam_binding.iam: Creation complete after 10s [id=b/XXXXXXXXXX/roles/storage.objectViewer]
google_compute_address.internal-ip-cc2: Still creating... [10s elapsed]
google_compute_address.internal-ip-cc2: Creation complete after 11s [id=projects/tacg-gcp-XXXXXXXXXX/regions/europe-west3/addresses/internal-ip-cc2]
google_compute_address.internal-ip-wmi: Creating...
google_compute_address.internal-ip-adminvm: Creation complete after 11s [id=projects/tacg-gcp-XXXXXXXXXX/regions/europe-west3/addresses/internal-ip-avm]
google_compute_address.internal-ip-cc1: Creating...
google_compute_firewall.Allow-All-ICMP: Creation complete after 11s [id=projects/tacg-gcp-XXXXXXXXXX/global/firewalls/tacg-gcp-tf-fw-allow-all-icmp]
google_compute_instance.CC2: Creating...

... **Output shortened ** ...

google_compute_firewall.Allow-WinRM: Creation complete after 11s [id=projects/tacg-gcp-XXXXXXXXXX/global/firewalls/tacg-gcp-tf-fw-allow-winrm]
google_compute_instance.AdminVM: Creating...
google_compute_firewall.Allow-All-UDP: Creation complete after 11s [id=projects/tacg-gcp-XXXXXXXXXX/global/firewalls/tacg-gcp-tf-fw-allow-all-udp]
google_compute_firewall.Allow-SSH: Creation complete after 11s [id=projects/tacg-gcp-XXXXXXXXXX/global/firewalls/tacg-gcp-tf-fw-allow-ssh]
google_compute_firewall.Allow-RDP: Creation complete after 11s [id=projects/tacg-gcp-XXXXXXXXXX/global/firewalls/tacg-gcp-tf-fw-allow-rdp]
google_compute_firewall.Allow-HTTP: Creation complete after 11s [id=projects/tacg-gcp-XXXXXXXXXX/global/firewalls/tacg-gcp-tf-fw-allow-http]
google_compute_firewall.Allow-HTTPS: Creation complete after 12s [id=projects/tacg-gcp-XXXXXXXXXX/global/firewalls/tacg-gcp-tf-fw-allow-https]
google_compute_firewall.Allow-All-TCP: Creation complete after 12s [id=projects/tacg-gcp-XXXXXXXXXX/global/firewalls/tacg-gcp-tf-fw-allow-all-tcp]
google_compute_address.internal-ip-wmi: Still creating... [10s elapsed]
google_compute_address.internal-ip-cc1: Still creating... [10s elapsed]
google_compute_instance.CC2: Still creating... [10s elapsed]
google_compute_instance.AdminVM: Still creating... [10s elapsed]
google_compute_address.internal-ip-wmi: Creation complete after 11s [id=projects/tacg-gcp-XXXXXXXXXX/regions/europe-west3/addresses/internal-ip-wmi]
google_compute_instance.WMI: Creating...
google_compute_address.internal-ip-cc1: Creation complete after 11s [id=projects/tacg-gcp-XXXXXXXXXX/regions/europe-west3/addresses/internal-ip-cc1]
google_compute_instance.CC1: Creating...
google_compute_instance.CC2: Creation complete after 13s [id=projects/tacg-gcp-XXXXXXXXXX/zones/europe-west3-c/instances/tacg-gcp-tf-cc2]
google_compute_instance.AdminVM: Creation complete after 13s [id=projects/tacg-gcp-XXXXXXXXXX/zones/europe-west3-c/instances/tacg-gcp-tf-avm]
google_compute_instance.WMI: Still creating... [10s elapsed]
google_compute_instance.CC1: Still creating... [10s elapsed]
google_compute_instance.WMI: Creation complete after 13s [id=projects/tacg-gcp-XXXXXXXXXX/zones/europe-west3-c/instances/tacg-gcp-tf-wmi]
google_compute_instance.CC1: Creation complete after 13s [id=projects/tacg-gcp-XXXXXXXXXX/zones/europe-west3-c/instances/tacg-gcp-tf-cc1]

Apply complete! Resources: 24 added, 0 changed, 0 destroyed.
PS C:\TACG\_CCOnGCP\_CCOnGCP-Creation>

As no errors occurred, Terraform has completed the creation and partial configuration of the relevant prerequisites on Google Cloud Platform (GCP).

Example of successful creation:
All VMs were successfully created:

gcp-vmscreated.png

If errors during VM creation or Domain-join occur, look at the output of the serial console of the erroneous VM: 
Example: The startup script could not run:
 
PS C:\TACG\_CCOnGCP\_CCOnGCP-Creation gcloud compute instances get-serial-port-output tacg-gcp-tf-avm --zone europe-west3-c
CSM BBS Table full.
BdsDxe: loading Boot0001 "UEFI Google PersistentDisk " from PciRoot(0x0)/Pci(0x3,0x0)/Scsi(0x1,0x0)
BdsDxe: starting Boot0001 "UEFI Google PersistentDisk " from PciRoot(0x0)/Pci(0x3,0x0)/Scsi(0x1,0x0)

UEFI: Attempting to start image.
Description: UEFI Google PersistentDisk
FilePath: PciRoot(0x0)/Pci(0x3,0x0)/Scsi(0x1,0x0)
OptionNumber: 1.
2024/03/22 12:24:08 GCEGuestAgent: GCE Agent Started (version 20240109.00)
2024/03/22 12:24:09 GCEGuestAgent: Adding route to metadata server on adapter with index 5
2024/03/22 12:24:09 GCEGuestAgent: Starting the scheduler to run jobs
2024/03/22 12:24:09 GCEGuestAgent: Scheduler - start: []
2024/03/22 12:24:09 GCEGuestAgent: Skipping scheduling credential generation job, failed to reach client credentials endpoint(instance/credentials/certs) with error: error connecting to metadata server, status code: 404
2024/03/22 12:24:09 GCEGuestAgent: Failed to schedule job MTLS_MDS_Credential_Boostrapper with error: ShouldEnable() returned false, cannot schedule job MTLS_MDS_Credential_Boostrapper
2024/03/22 12:24:09 GCEGuestAgent: Starting the scheduler to run jobs
2024/03/22 12:24:10 GCEGuestAgent: Scheduling job: telemetryJobID
2024/03/22 12:24:10 GCEGuestAgent: Scheduling job "telemetryJobID" to run at 24.000000 hr interval
2024/03/22 12:24:10 GCEGuestAgent: Successfully scheduled job telemetryJobID
2024/03/22 12:25:01 GCEInstanceSetup: Enable google_osconfig_agent during the specialize configuration pass.
2024/03/22 12:25:04 GCEInstanceSetup: Starting sysprep specialize phase.
2024/03/22 12:25:05 GCEInstanceSetup: All networks set to DHCP.
2024/03/22 12:25:05 GCEInstanceSetup: VirtIO network adapter detected.
2024/03/22 12:25:08 GCEInstanceSetup: MTU set to 1460 for IPv4 and IPv6 using PowerShell for interface 5 - Google VirtIO Ethernet Adapter. Build 20348
2024/03/22 12:25:08 GCEInstanceSetup: Running 'route' with arguments '/p add 169.254.169.254 mask 255.255.255.255 0.0.0.0 if 5 metric 1'
2024/03/22 12:25:08 GCEInstanceSetup: --> OK!
2024/03/22 12:25:08 GCEInstanceSetup: Added persistent route to metadata netblock to netkvm adapter.
2024/03/22 12:25:08 GCEInstanceSetup: Getting hostname from metadata server.
2024/03/22 12:25:08 GCEInstanceSetup: Renamed from WIN-6LJJ4HG704O to tacg-gcp-tf-avm.
2024/03/22 12:25:08 GCEInstanceSetup: Configuring WinRM...
2024/03/22 12:25:09 GCEInstanceSetup: Running 'C:\Program Files\Google\Compute Engine\tools\certgen.exe' with arguments '-outDir C:\Windows\TEMP\cert -hostname tacg-gcp-tf-avm'
2024/03/22 12:25:10 GCEInstanceSetup: --> written C:\Windows\TEMP\cert\cert.p12
2024/03/22 12:25:10 GCEInstanceSetup: Waiting for WinRM to be running...
2024/03/22 12:25:11 GCEInstanceSetup: Setup of WinRM complete.
2024/03/22 12:25:11 C:\Program Files\Google\Compute Engine\metadata_scripts\GCEMetadataScripts.exe: Starting specialize scripts (version dev).
2024/03/22 12:25:11 C:\Program Files\Google\Compute Engine\metadata_scripts\GCEMetadataScripts.exe: Found sysprep-specialize-script-url in metadata.
2024/03/22 12:25:15 C:\Program Files\Google\Compute Engine\metadata_scripts\GCEMetadataScripts.exe: Failed to download GCS path: error reading object "domain-join-script.ps1": Get "https://storage.googleapis.com/XXXXXXXXXX/domain-join-script.ps1": metadata: GCE metadata "instance/service-accounts/default/token?scopes=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fdevstorage.full_control%2Chttps%3A%2F%2Fwww.googleapis.com%2Fauth%2Fcloud-platform" not defined
2024/03/22 12:25:15 C:\Program Files\Google\Compute Engine\metadata_scripts\GCEMetadataScripts.exe: Trying unauthenticated download
2024/03/22 12:25:28 C:\Program Files\Google\Compute Engine\metadata_scripts\GCEMetadataScripts.exe: sysprep-specialize-script-url: The network path was not found.
2024/03/22 12:25:28 C:\Program Files\Google\Compute Engine\metadata_scripts\GCEMetadataScripts.exe: sysprep-specialize-script-url:
2024/03/22 12:25:28 C:\Program Files\Google\Compute Engine\metadata_scripts\GCEMetadataScripts.exe: sysprep-specialize-script-url: The command failed to complete successfully.
2024/03/22 12:25:28 C:\Program Files\Google\Compute Engine\metadata_scripts\GCEMetadataScripts.exe: sysprep-specialize-script-url:
2024/03/22 12:25:28 C:\Program Files\Google\Compute Engine\metadata_scripts\GCEMetadataScripts.exe: sysprep-specialize-script-url exit status 0
2024/03/22 12:25:28 C:\Program Files\Google\Compute Engine\metadata_scripts\GCEMetadataScripts.exe: Finished running specialize scripts.
2024/03/22 12:25:28 GCEInstanceSetup: Finished with sysprep specialize phase, restarting...
2024/03/22 12:25:44 GCEGuestAgent: Error watching metadata: context canceled
2024/03/22 12:25:44 GCEGuestAgent: GCE Agent Stopped
CSM BBS Table full.
BdsDxe: loading Boot0003 "Windows Boot Manager" from HD(2,GPT,AB360148-CB4B-41C0-88C9-F1583EB09E72,0x8000,0x32000)/\EFI\Microsoft\Boot\bootmgfw.efi
BdsDxe: starting Boot0003 "Windows Boot Manager" from HD(2,GPT,AB360148-CB4B-41C0-88C9-F1583EB09E72,0x8000,0x32000)/\EFI\Microsoft\Boot\bootmgfw.efi

UEFI: Attempting to start image.
Description: Windows Boot Manager
FilePath: HD(2,GPT,AB360148-CB4B-41C0-88C9-F1583EB09E72,0x8000,0x32000)/\EFI\Microsoft\Boot\bootmgfw.efi
OptionNumber: 3.
2024/03/22 12:26:46 GCEGuestAgent: GCE Agent Started (version 20240109.00)
2024/03/22 12:26:47 GCEGuestAgent: Starting the scheduler to run jobs
2024/03/22 12:26:47 GCEGuestAgent: Scheduler - start: []
2024/03/22 12:26:47 GCEGuestAgent: Skipping scheduling credential generation job, failed to reach client credentials endpoint(instance/credentials/certs) with error: error connecting to metadata server, status code: 404
2024/03/22 12:26:47 GCEGuestAgent: Failed to schedule job MTLS_MDS_Credential_Boostrapper with error: ShouldEnable() returned false, cannot schedule job MTLS_MDS_Credential_Boostrapper
2024/03/22 12:26:47 GCEGuestAgent: Starting the scheduler to run jobs
2024-03-22T12:26:48.0295Z OSConfigAgent Info: OSConfig Agent (version 20231207.01.0+win@1) started.
2024/03/22 12:26:48 GCEGuestAgent: Scheduling job: telemetryJobID
2024/03/22 12:26:48 GCEGuestAgent: Scheduling job "telemetryJobID" to run at 24.000000 hr interval
2024/03/22 12:26:48 GCEGuestAgent: Successfully scheduled job telemetryJobID
2024/03/22 12:26:48 GCEGuestAgent: Scheduler - added: [now 2024-03-22 12:26:48.2174847 +0000 GMT entry 1 next 2024-03-23 12:26:48 +0000 GMT]


Specify --start=6654 in the next get-serial-port-output invocation to get only the new output starting from here.
PS C:\TACG\_CCOnGCP\_CCOnGCP-Creation 

Now, the next step can be started.

Module 2: Install and Configure all Resources in Google Cloud Platform (GCP)

IMPORTANT: This module can only be run on the previously created Administrative VM as the deployment of steps 2 and 3 relies heavily on WinRM! Please install Terraform on the Administrative VM before trying to run the scripts!

This module is split into the following configuration parts:

  • Configuring the three previously created Virtual Machines on Google Cloud Platform (GCP):

    • Installing the needed software on the Cloud Connectors
    • Installing the needed software on the Admin-VM
  • Creating the necessary Resources in Citrix Cloud:

    • Creating a Resource Location in Citrix Cloud
    • Configuring the 2 Cloud Connectors
    • Registering the 2 Cloud Connectors in the newly created Resource Location

NOTE: This module has some complexity as many automated actions and many different entities are created on Google Cloud Platform (GCP) and run on the VMs. It contains a mixture of PowerShell scripts, REST-API calls and Terraform scripts to achieve a working deployment.

Our provider does not currently support creating a Resource Location on Citrix Cloud. Therefore, we use a PowerShell script to create one using a REST-API call.

Please make sure you have configured the variables according to your needs by using the corresponding .auto.tfvars.json file.

Terraform runs various scripts before creating the Cloud Connectors' configuration to determine needed information, such as the Site ID, the Zone ID, and the Resource Location ID.
These IDs are used in other scripts or files - for example, the parameter file for deploying the Cloud Connector needs the Resource Location ID of the Resource Location, which Terraform creates automatically. Unfortunately, the REST-API provider does not return the ID of the newly created Resource Location, so we need to run PowerShell after the creation of the Resource Location:

Example scripts - creating the configuration file for the unattended installation of the Cloud Controller software:
At first, Terraform writes the configuration file without the Resource Location ID as the Resource Location is created later:

### Create CWC-Installer configuration file based on variables and save it into Transfer directory
resource "local_file" "CWC-Configuration" {
  depends_on = [restapi_object.CreateRL]
  content  = jsonencode(
        {
        "customerName" = "${var.CC_CustomerID}",
        "clientId" = "${var.CC_APIKey-ClientID}",
        "clientSecret" = "${var.CC_APIKey-ClientSecret}",
        "resourceLocationId" = "XXXXXXXXXX",
        "acceptTermsOfService" = true
        }
      )
  filename = "${var.CC_Install_LogPath}/DATA/cwc.json"
 
}

After installing further pre-requisites and creating the Resource Location, Terraform runs a PowerShell script to get the needed ID and updates the configuration file for the CWC installer:

#### Change RL-ID in CWC-JSON file to valid Zone-ID
resource "local_file" "CreateValidCWCOnAVM" {
  depends_on = [ terraform_data.ZoneID2 ]
content  = <<-EOT
Add-Content ${var.CC_Install_LogPath}/log.txt "`nCWC-Script started."
#### Need to invoke PowerShell as Domain User as the provisioner does not allow to be run in a Domain Users-context
$PSUsername = '${var.Provisioner_DomainAdmin-Username}'
$PSPassword = '${var.Provisioner_DomainAdmin-Password}'
$PSSecurePassword = ConvertTo-SecureString $PSPassword -AsPlainText -Force
$PSCredential = New-Object System.Management.Automation.PSCredential ($PSUsername, $PSSecurePassword)
Invoke-Command -ComputerName localhost -Credential $PSCredential -ScriptBlock {
$path = "${var.CC_Install_LogPath}"
# Correct the Resource Location ID in cwc.json file
$requestUri = "https://api-eu.cloud.com/resourcelocations"
$CCBearerToken = Get-Content -Path ${var.CC_Install_LogPath}/DATA/GetBT.txt -Force
$CCSiteID = Get-Content -Path ${var.CC_Install_LogPath}/DATA/GetSiteID.txt -Force
$CCZoneID = Get-Content -Path ${var.CC_Install_LogPath}/DATA/GetZoneID.txt -Force
$headers = @{ "Accept"="application/json"; "Authorization" =  $CCBearerToken; "Citrix-CustomerId" = "${var.CC_CustomerID}"}
$response = Invoke-RestMethod -Uri $requestUri -Method GET -Headers $headers | Convertto-Json
Add-Content ${var.CC_Install_LogPath}/log.txt "`nCWC-Response: $response"
$RLs = ConvertFrom-Json $response
$RLFiltered = $RLs.items | Where-Object name -in "${var.CC_RestRLName}"
Add-Content ${var.CC_Install_LogPath}/log.txt $RLFiltered
$RLID = $RLFiltered.id
$OrigContent = Get-Content ${var.CC_Install_LogPath}/DATA/cwc.json
Add-Content ${var.CC_Install_LogPath}/log.txt $RLID
Add-Content ${var.CC_Install_LogPath}/log.txt $OrigContent
$CorrContent = $OrigCOntent.Replace('XXXXXXXXXX', $RLID) | Out-File -FilePath ${var.CC_Install_LogPath}/DATA/cwc.json -NoNewline -Encoding Ascii
$PathCompl = "${var.CC_Install_LogPath}/DATA/GetRLID.txt"
Set-Content -Path $PathCompl -Value $RLID -NoNewline -Encoding Ascii
Add-Content ${var.CC_Install_LogPath}/log.txt "`ncwc.json corrected."
Add-Content ${var.CC_Install_LogPath}/log.txt "`nCWC-Script completed."
}
EOT
filename = "${var.CC_Install_LogPath}/DATA/CreateValidCWCOnAVM.ps1"
}

The Terraform configuration contains some idle time slots to make sure that background operations on Google Cloud Platform (GCP) or the VMs can completed before the next configuration steps occur.


We have seen different elapsed configuration times related to different loads on the Google Cloud Platform (GCP) systems!

Before running Terraform, we cannot see the Resource Location:

cc-no-rl.png

 

The configuration can be started by following the normal Terraform workflow:
terraform init,
terraform plan
and if no errors occur

terraform apply

 

PS C:\TACG\_CCOnGCP\_CCOnGCP-Install> terraform init

Initializing the backend...

Initializing provider plugins...
- terraform.io/builtin/terraform is built in to Terraform
- Finding latest version of hashicorp/time...
- Finding latest version of hashicorp/null...
- Finding hashicorp/google versions matching ">= 5.21.0"...
- Finding citrix/citrix versions matching ">= 0.5.4"...
- Finding latest version of hashicorp/local...
- Installing hashicorp/time v0.11.1...
- Installed hashicorp/time v0.11.1 (signed by HashiCorp)
- Installing hashicorp/null v3.2.2...
- Installed hashicorp/null v3.2.2 (signed by HashiCorp)
- Installing hashicorp/google v5.22.0...
- Installed hashicorp/google v5.22.0 (signed by HashiCorp)
- Installing citrix/citrix v0.5.4...
- Installed citrix/citrix v0.5.4 (signed by a HashiCorp partner, key ID 25D62DD8407EA386)
- Installing hashicorp/local v2.5.1...
- Installed hashicorp/local v2.5.1 (signed by HashiCorp)

Partner and community providers are signed by their developers.
If you'd like to know more about provider signing, you can read about it here:
https://www.terraform.io/docs/cli/plugins/signing.html

Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.

PS C:\TACG\_CCOnGCP\_CCOnGCP-Install> terraform plan
data.google_compute_address.IPOfCC1: Reading...
data.google_compute_address.IPOfCC2: Reading...
data.google_compute_address.IPOfCC1: Read complete after 0s [id=projects/tacg-gcp-XXXXXXXXXX/regions/europe-west3/addresses/internal-ip-cc1]
data.google_compute_address.IPOfCC2: Read complete after 0s [id=projects/tacg-gcp-XXXXXXXXXX/regions/europe-west3/addresses/internal-ip-cc2]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the
following symbols:
  + create

Terraform will perform the following actions:

  # local_file.CWC-Configuration will be created
  + resource "local_file" "CWC-Configuration" {
      + content              = (sensitive value)
      + content_base64sha256 = (known after apply)
      + content_base64sha512 = (known after apply)
      + content_md5          = (known after apply)
      + content_sha1         = (known after apply)
      + content_sha256       = (known after apply)
      + content_sha512       = (known after apply)
      + directory_permission = "0777"
      + file_permission      = "0777"
      + filename             = "c:/temp/xdinst/DATA/cwc.json"
      + id                   = (known after apply)
    }

  # local_file.CreateRLScript will be created
  + resource "local_file" "CreateRLScript" {
      + content              = (sensitive value)
      + content_base64sha256 = (known after apply)
      + content_base64sha512 = (known after apply)
      + content_md5          = (known after apply)
      + content_sha1         = (known after apply)
      + content_sha256       = (known after apply)
      + content_sha512       = (known after apply)
      + directory_permission = "0777"
      + file_permission      = "0777"
      + filename             = "c:/temp/xdinst/DATA/CreateRL.ps1"
      + id                   = (known after apply)
    }

  # local_file.CreateValidCWCOnAVM will be created
  + resource "local_file" "CreateValidCWCOnAVM" {
      + content              = (sensitive value)
      + content_base64sha256 = (known after apply)
      + content_base64sha512 = (known after apply)
      + content_md5          = (known after apply)
      + content_sha1         = (known after apply)
      + content_sha256       = (known after apply)
      + content_sha512       = (known after apply)
      + directory_permission = "0777"
      + file_permission      = "0777"
      + filename             = "c:/temp/xdinst/DATA/CreateValidCWCOnAVM.ps1"
      + id                   = (known after apply)
    }

  # local_file.GetBearerToken will be created
  + resource "local_file" "GetBearerToken" {
      + content              = (sensitive value)
      + content_base64sha256 = (known after apply)
      + content_base64sha512 = (known after apply)
      + content_md5          = (known after apply)
      + content_sha1         = (known after apply)
      + content_sha256       = (known after apply)
      + content_sha512       = (known after apply)
      + directory_permission = "0777"
      + file_permission      = "0777"
      + filename             = "c:/temp/xdinst/DATA/GetBT.ps1"
      + id                   = (known after apply)
    }

  # local_file.GetSiteIDScript will be created
  + resource "local_file" "GetSiteIDScript" {
      + content              = (sensitive value)
      + content_base64sha256 = (known after apply)
      + content_base64sha512 = (known after apply)
      + content_md5          = (known after apply)
      + content_sha1         = (known after apply)
      + content_sha256       = (known after apply)
      + content_sha512       = (known after apply)
      + directory_permission = "0777"
      + file_permission      = "0777"
      + filename             = "c:/temp/xdinst/DATA/GetSiteID.ps1"
      + id                   = (known after apply)
    }

  # local_file.GetZoneIDScript will be created
  + resource "local_file" "GetZoneIDScript" {
      + content              = (sensitive value)
      + content_base64sha256 = (known after apply)
      + content_base64sha512 = (known after apply)
      + content_md5          = (known after apply)
      + content_sha1         = (known after apply)
      + content_sha256       = (known after apply)
      + content_sha512       = (known after apply)
      + directory_permission = "0777"
      + file_permission      = "0777"
      + filename             = "c:/temp/xdinst/DATA/GetZoneID.ps1"
      + id                   = (known after apply)
    }

  # local_file.InstallCWCOnCC will be created
  + resource "local_file" "InstallCWCOnCC" {
      + content              = (sensitive value)
      + content_base64sha256 = (known after apply)
      + content_base64sha512 = (known after apply)
      + content_md5          = (known after apply)
      + content_sha1         = (known after apply)
      + content_sha256       = (known after apply)
      + content_sha512       = (known after apply)
      + directory_permission = "0777"
      + file_permission      = "0777"
      + filename             = "c:/temp/xdinst/DATA/InstallCWCOnCC.ps1"
      + id                   = (known after apply)
    }

  # local_file.InstallPoSHSDKOnAVM will be created
  + resource "local_file" "InstallPoSHSDKOnAVM" {
      + content              = (sensitive value)
      + content_base64sha256 = (known after apply)
      + content_base64sha512 = (known after apply)
      + content_md5          = (known after apply)
      + content_sha1         = (known after apply)
      + content_sha256       = (known after apply)
      + content_sha512       = (known after apply)
      + directory_permission = "0777"
      + file_permission      = "0777"
      + filename             = "c:/temp/xdinst/DATA/InstallPoSHSDKOnAVM.ps1"
      + id                   = (known after apply)
    }

  # local_file.Log will be created
  + resource "local_file" "Log" {
      + content              = "Directory created."
      + content_base64sha256 = (known after apply)
      + content_base64sha512 = (known after apply)
      + content_md5          = (known after apply)
      + content_sha1         = (known after apply)
      + content_sha256       = (known after apply)
      + content_sha512       = (known after apply)
      + directory_permission = "0777"
      + file_permission      = "0777"
      + filename             = "c:/temp/xdinst/log.txt"
      + id                   = (known after apply)
    }

  # local_file.LogData will be created
  + resource "local_file" "LogData" {
      + content              = "Directory created."
      + content_base64sha256 = (known after apply)
      + content_base64sha512 = (known after apply)
      + content_md5          = (known after apply)
      + content_sha1         = (known after apply)
      + content_sha256       = (known after apply)
      + content_sha512       = (known after apply)
      + directory_permission = "0777"
      + file_permission      = "0777"
      + filename             = "c:/temp/xdinst/DATA/log.txt"
      + id                   = (known after apply)
    }

  # local_file.RestartCC will be created
  + resource "local_file" "RestartCC" {
      + content              = (sensitive value)
      + content_base64sha256 = (known after apply)
      + content_base64sha512 = (known after apply)
      + content_md5          = (known after apply)
      + content_sha1         = (known after apply)
      + content_sha256       = (known after apply)
      + content_sha512       = (known after apply)
      + directory_permission = "0777"
      + file_permission      = "0777"
      + filename             = "c:/temp/xdinst/DATA/RestartCC.ps1"
      + id                   = (known after apply)
    }

  # null_resource.CallRebootScriptOnCC1 will be created
  + resource "null_resource" "CallRebootScriptOnCC1" {
      + id = (known after apply)
    }

  # null_resource.CallRebootScriptOnCC2 will be created
  + resource "null_resource" "CallRebootScriptOnCC2" {
      + id = (known after apply)
    }

  # null_resource.CallRequiredScriptsOnCC1 will be created
  + resource "null_resource" "CallRequiredScriptsOnCC1" {
      + id = (known after apply)
    }

  # null_resource.CallRequiredScriptsOnCC2 will be created
  + resource "null_resource" "CallRequiredScriptsOnCC2" {
      + id = (known after apply)
    }

  # null_resource.ExecuteInstallPoSHSDKOnAVM will be created
  + resource "null_resource" "ExecuteInstallPoSHSDKOnAVM" {
      + id = (known after apply)
    }

  # null_resource.UploadRequiredComponentsToCC1 will be created
  + resource "null_resource" "UploadRequiredComponentsToCC1" {
      + id = (known after apply)
    }

  # null_resource.UploadRequiredComponentsToCC2 will be created
  + resource "null_resource" "UploadRequiredComponentsToCC2" {
      + id = (known after apply)
    }

  # terraform_data.ExecuteCreateValidCWCOnAVM will be created
  + resource "terraform_data" "ExecuteCreateValidCWCOnAVM" {
      + id = (known after apply)
    }

  # terraform_data.GetBT will be created
  + resource "terraform_data" "GetBT" {
      + id = (known after apply)
    }

  # terraform_data.ResourceLocation will be created
  + resource "terraform_data" "ResourceLocation" {
      + id = (known after apply)
    }

  # terraform_data.SiteID will be created
  + resource "terraform_data" "SiteID" {
      + id = (known after apply)
    }

  # terraform_data.ZoneID will be created
  + resource "terraform_data" "ZoneID" {
      + id = (known after apply)
    }

  # terraform_data.ZoneID2 will be created
  + resource "terraform_data" "ZoneID2" {
      + id = (known after apply)
    }

  # time_sleep.wait_1800_seconds_CC1 will be created
  + resource "time_sleep" "wait_1800_seconds_CC1" {
      + create_duration = "1800s"
      + id              = (known after apply)
    }

  # time_sleep.wait_1800_seconds_CC2 will be created
  + resource "time_sleep" "wait_1800_seconds_CC2" {
      + create_duration = "1800s"
      + id              = (known after apply)
    }

  # time_sleep.wait_60_seconds will be created
  + resource "time_sleep" "wait_60_seconds" {
      + create_duration = "60s"
      + id              = (known after apply)
    }

  # time_sleep.wait_900_seconds will be created
  + resource "time_sleep" "wait_900_seconds" {
      + create_duration = "900s"
      + id              = (known after apply)
    }

Plan: 30 to add, 0 to change, 0 to destroy.

Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if
you run "terraform apply" now.

PS C:\TACG\_CCOnGCP\_CCOnGCP-Install> terraform apply
data.google_compute_address.IPOfCC2: Reading...
data.google_compute_address.IPOfCC1: Reading...
data.google_compute_address.IPOfCC1: Read complete after 0s [id=projects/tacg-gcp-XXXXXXXXXX/regions/europe-west3/addresses/internal-ip-cc1]
data.google_compute_address.IPOfCC2: Read complete after 0s [id=projects/tacg-gcp-XXXXXXXXXX/regions/europe-west3/addresses/internal-ip-cc2]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # local_file.CWC-Configuration will be created
  + resource "local_file" "CWC-Configuration" {
      + content              = (sensitive value)
      + content_base64sha256 = (known after apply)
      + content_base64sha512 = (known after apply)
      + content_md5          = (known after apply)
      + content_sha1         = (known after apply)
      + content_sha256       = (known after apply)
      + content_sha512       = (known after apply)
      + directory_permission = "0777"
      + file_permission      = "0777"
      + filename             = "c:/temp/xdinst/DATA/cwc.json"
      + id                   = (known after apply)
    }

  # local_file.CreateRLScript will be created
  + resource "local_file" "CreateRLScript" {
      + content              = (sensitive value)
      + content_base64sha256 = (known after apply)
      + content_base64sha512 = (known after apply)
      + content_md5          = (known after apply)
      + content_sha1         = (known after apply)
      + content_sha256       = (known after apply)
      + content_sha512       = (known after apply)
      + directory_permission = "0777"
      + file_permission      = "0777"
      + filename             = "c:/temp/xdinst/DATA/CreateRL.ps1"
      + id                   = (known after apply)
    }

  # local_file.CreateValidCWCOnAVM will be created
  + resource "local_file" "CreateValidCWCOnAVM" {
      + content              = (sensitive value)
      + content_base64sha256 = (known after apply)
      + content_base64sha512 = (known after apply)
      + content_md5          = (known after apply)
      + content_sha1         = (known after apply)
      + content_sha256       = (known after apply)
      + content_sha512       = (known after apply)
      + directory_permission = "0777"
      + file_permission      = "0777"
      + filename             = "c:/temp/xdinst/DATA/CreateValidCWCOnAVM.ps1"
      + id                   = (known after apply)
    }

  # local_file.GetBearerToken will be created
  + resource "local_file" "GetBearerToken" {
      + content              = (sensitive value)
      + content_base64sha256 = (known after apply)
      + content_base64sha512 = (known after apply)
      + content_md5          = (known after apply)
      + content_sha1         = (known after apply)
      + content_sha256       = (known after apply)
      + content_sha512       = (known after apply)
      + directory_permission = "0777"
      + file_permission      = "0777"
      + filename             = "c:/temp/xdinst/DATA/GetBT.ps1"
      + id                   = (known after apply)
    }

  # local_file.GetSiteIDScript will be created
  + resource "local_file" "GetSiteIDScript" {
      + content              = (sensitive value)
      + content_base64sha256 = (known after apply)
      + content_base64sha512 = (known after apply)
      + content_md5          = (known after apply)
      + content_sha1         = (known after apply)
      + content_sha256       = (known after apply)
      + content_sha512       = (known after apply)
      + directory_permission = "0777"
      + file_permission      = "0777"
      + filename             = "c:/temp/xdinst/DATA/GetSiteID.ps1"
      + id                   = (known after apply)
    }

  # local_file.GetZoneIDScript will be created
  + resource "local_file" "GetZoneIDScript" {
      + content              = (sensitive value)
      + content_base64sha256 = (known after apply)
      + content_base64sha512 = (known after apply)
      + content_md5          = (known after apply)
      + content_sha1         = (known after apply)
      + content_sha256       = (known after apply)
      + content_sha512       = (known after apply)
      + directory_permission = "0777"
      + file_permission      = "0777"
      + filename             = "c:/temp/xdinst/DATA/GetZoneID.ps1"
      + id                   = (known after apply)
    }

  # local_file.InstallCWCOnCC will be created
  + resource "local_file" "InstallCWCOnCC" {
      + content              = (sensitive value)
      + content_base64sha256 = (known after apply)
      + content_base64sha512 = (known after apply)
      + content_md5          = (known after apply)
      + content_sha1         = (known after apply)
      + content_sha256       = (known after apply)
      + content_sha512       = (known after apply)
      + directory_permission = "0777"
      + file_permission      = "0777"
      + filename             = "c:/temp/xdinst/DATA/InstallCWCOnCC.ps1"
      + id                   = (known after apply)
    }

  # local_file.InstallPoSHSDKOnAVM will be created
  + resource "local_file" "InstallPoSHSDKOnAVM" {
      + content              = (sensitive value)
      + content_base64sha256 = (known after apply)
      + content_base64sha512 = (known after apply)
      + content_md5          = (known after apply)
      + content_sha1         = (known after apply)
      + content_sha256       = (known after apply)
      + content_sha512       = (known after apply)
      + directory_permission = "0777"
      + file_permission      = "0777"
      + filename             = "c:/temp/xdinst/DATA/InstallPoSHSDKOnAVM.ps1"
      + id                   = (known after apply)
    }

  # local_file.Log will be created
  + resource "local_file" "Log" {
      + content              = "Directory created."
      + content_base64sha256 = (known after apply)
      + content_base64sha512 = (known after apply)
      + content_md5          = (known after apply)
      + content_sha1         = (known after apply)
      + content_sha256       = (known after apply)
      + content_sha512       = (known after apply)
      + directory_permission = "0777"
      + file_permission      = "0777"
      + filename             = "c:/temp/xdinst/log.txt"
      + id                   = (known after apply)
    }

  # local_file.LogData will be created
  + resource "local_file" "LogData" {
      + content              = "Directory created."
      + content_base64sha256 = (known after apply)
      + content_base64sha512 = (known after apply)
      + content_md5          = (known after apply)
      + content_sha1         = (known after apply)
      + content_sha256       = (known after apply)
      + content_sha512       = (known after apply)
      + directory_permission = "0777"
      + file_permission      = "0777"
      + filename             = "c:/temp/xdinst/DATA/log.txt"
      + id                   = (known after apply)
    }

  # null_resource.CallRequiredScriptsOnCC1 will be created
  + resource "null_resource" "CallRequiredScriptsOnCC1" {
      + id = (known after apply)
    }

  # null_resource.CallRequiredScriptsOnCC2 will be created
  + resource "null_resource" "CallRequiredScriptsOnCC2" {
      + id = (known after apply)
    }

  # null_resource.ExecuteInstallPoSHSDKOnAVM will be created
  + resource "null_resource" "ExecuteInstallPoSHSDKOnAVM" {
      + id = (known after apply)
    }

  # null_resource.UploadRequiredComponentsToCC1 will be created
  + resource "null_resource" "UploadRequiredComponentsToCC1" {
      + id = (known after apply)
    }

  # null_resource.UploadRequiredComponentsToCC2 will be created
  + resource "null_resource" "UploadRequiredComponentsToCC2" {
      + id = (known after apply)
    }

  # terraform_data.ExecuteCreateValidCWCOnAVM will be created
  + resource "terraform_data" "ExecuteCreateValidCWCOnAVM" {
      + id = (known after apply)
    }

  # terraform_data.GetBT will be created
  + resource "terraform_data" "GetBT" {
      + id = (known after apply)
    }

  # terraform_data.ResourceLocation will be created
  + resource "terraform_data" "ResourceLocation" {
      + id = (known after apply)
    }

  # terraform_data.SiteID will be created
  + resource "terraform_data" "SiteID" {
      + id = (known after apply)
    }

# terraform_data.ZoneID2 will be created
  + resource "terraform_data" "ZoneID2" {
      + id = (known after apply)
    }

  # time_sleep.wait_60_seconds will be created
  + resource "time_sleep" "wait_60_seconds" {
      + create_duration = "60s"
      + id              = (known after apply)
    }

  # time_sleep.wait_900_seconds will be created
  + resource "time_sleep" "wait_900_seconds" {
      + create_duration = "900s"
      + id              = (known after apply)
    }

  # local_file.InstallCWCOnCC will be created
  + resource "local_file" "InstallCWCOnCC" {
      + content              = (sensitive value)
      + content_base64sha256 = (known after apply)
      + content_base64sha512 = (known after apply)
      + content_md5          = (known after apply)
      + content_sha1         = (known after apply)
      + content_sha256       = (known after apply)
      + content_sha512       = (known after apply)
      + directory_permission = "0777"
      + file_permission      = "0777"
      + filename             = "c:/temp/xdinst/DATA/InstallCWCOnCC.ps1"
      + id                   = (known after apply)
    }

  # local_file.Log will be created
  + resource "local_file" "Log" {
      + content              = "Directory created."
      + content_base64sha256 = (known after apply)
      + content_base64sha512 = (known after apply)
      + content_md5          = (known after apply)
      + content_sha1         = (known after apply)
      + content_sha256       = (known after apply)
      + content_sha512       = (known after apply)
      + directory_permission = "0777"
      + file_permission      = "0777"
      + filename             = "c:/temp/xdinst/log.txt"
      + id                   = (known after apply)
    }

  # local_file.LogData will be created
  + resource "local_file" "LogData" {
      + content              = "Directory created."
      + content_base64sha256 = (known after apply)
      + content_base64sha512 = (known after apply)
      + content_md5          = (known after apply)
      + content_sha1         = (known after apply)
      + content_sha256       = (known after apply)
      + content_sha512       = (known after apply)
      + directory_permission = "0777"
      + file_permission      = "0777"
      + filename             = "c:/temp/xdinst/DATA/log.txt"
      + id                   = (known after apply)
    }

  # null_resource.CallRequiredScriptsOnCC1 will be created
  + resource "null_resource" "CallRequiredScriptsOnCC1" {
      + id = (known after apply)
    }

  # null_resource.CallRequiredScriptsOnCC2 will be created
  + resource "null_resource" "CallRequiredScriptsOnCC2" {
      + id = (known after apply)
    }

  # null_resource.UploadRequiredComponentsToCC1 will be created
  + resource "null_resource" "UploadRequiredComponentsToCC1" {
      + id = (known after apply)
    }

  # null_resource.UploadRequiredComponentsToCC2 will be created
  + resource "null_resource" "UploadRequiredComponentsToCC2" {
      + id = (known after apply)
    }

Plan: 30 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

null_resource.ExecuteInstallPoSHSDKOnAVM: Creating...
null_resource.ExecuteInstallPoSHSDKOnAVM: Provisioning with 'local-exec'...
null_resource.ExecuteInstallPoSHSDKOnAVM (local-exec): Executing: ["PowerShell" "-Command" " c:/temp/xdinst/DATA/InstallPoSHSDKOnAVM.ps1"]
local_file.GetBearerToken: Creating...
local_file.Log: Creating...
local_file.InstallPoSHSDKOnAVM: Creating...
local_file.Log: Creation complete after 0s [id=d725ce92ca8335439a5d83acf47f3ca2c957a515]
local_file.CWC-Configuration: Creating...
local_file.InstallPoSHSDKOnAVM: Creation complete after 0s [id=e384e2f2f26dd62c79ac8e6f0c25b98c1a82008c]
local_file.GetBearerToken: Creation complete after 0s [id=d97ec7cb0f4a1247b43864df53c69c7956507eb5]
local_file.CWC-Configuration: Creation complete after 0s [id=0257d2b92f197fa4d043b6cd4c4959be284dddc2]
local_file.LogData: Creating...
local_file.LogData: Creation complete after 0s [id=d725ce92ca8335439a5d83acf47f3ca2c957a515]
null_resource.ExecuteInstallPoSHSDKOnAVM: Still creating... [10s elapsed]
null_resource.ExecuteInstallPoSHSDKOnAVM: Still creating... [20s elapsed]

... ** Output shortened ** ... 

null_resource.ExecuteInstallPoSHSDKOnAVM: Still creating... [3m50s elapsed]
null_resource.ExecuteInstallPoSHSDKOnAVM: Still creating... [4m0s elapsed]
null_resource.ExecuteInstallPoSHSDKOnAVM: Creation complete after 4m4s [id=963532241712303576]
terraform_data.GetBT: Creating...
terraform_data.GetBT: Provisioning with 'local-exec'...
terraform_data.GetBT (local-exec): Executing: ["PowerShell" "-File" "c:/temp/xdinst/DATA/GetBT.ps1"]
terraform_data.GetBT: Creation complete after 10s [id=60c49067-1da2-caf5-1893-d4978527619f]
local_file.CreateRLScript: Creating...
local_file.CreateRLScript: Creation complete after 0s [id=e71a25d1aa907068420b19c3c61b58f54e256512]
terraform_data.ResourceLocation: Creating...
terraform_data.ResourceLocation: Provisioning with 'local-exec'...
terraform_data.ResourceLocation (local-exec): Executing: ["PowerShell" "-File" "c:/temp/xdinst/DATA/CreateRL.ps1"]
terraform_data.ResourceLocation: Creation complete after 1s [id=3d058474-6e42-69d2-a211-ce552bef2775]
time_sleep.wait_900_seconds: Creating...
time_sleep.wait_60_seconds: Creating...
time_sleep.wait_900_seconds: Still creating... [10s elapsed]
time_sleep.wait_60_seconds: Still creating... [10s elapsed]
time_sleep.wait_900_seconds: Still creating... [20s elapsed]
time_sleep.wait_60_seconds: Still creating... [20s elapsed]
time_sleep.wait_900_seconds: Still creating... [30s elapsed]
time_sleep.wait_60_seconds: Still creating... [30s elapsed]
time_sleep.wait_900_seconds: Still creating... [40s elapsed]
time_sleep.wait_60_seconds: Still creating... [40s elapsed]
time_sleep.wait_900_seconds: Still creating... [50s elapsed]
time_sleep.wait_60_seconds: Still creating... [51s elapsed]
time_sleep.wait_900_seconds: Still creating... [1m0s elapsed]
time_sleep.wait_60_seconds: Still creating... [1m1s elapsed]
time_sleep.wait_60_seconds: Creation complete after 1m1s [id=2024-03-27T14:27:37Z]
local_file.GetSiteIDScript: Creating...
local_file.GetSiteIDScript: Creation complete after 0s [id=9d4d1c344ddddcdeacfbbf4e84525c1cbc6a8059]
terraform_data.SiteID: Creating...
terraform_data.SiteID: Provisioning with 'local-exec'...
terraform_data.SiteID (local-exec): Executing: ["PowerShell" "-File" "c:/temp/xdinst/DATA/GetSiteID.ps1"]
terraform_data.SiteID: Creation complete after 1s [id=6a5d6c03-cfa2-8b27-0ca4-c144fb1b44ee]
time_sleep.wait_900_seconds: Still creating... [1m11s elapsed]
time_sleep.wait_900_seconds: Still creating... [1m21s elapsed]
time_sleep.wait_900_seconds: Still creating... [1m31s elapsed]

... ** Output shortened ** ... 

time_sleep.wait_900_seconds: Still creating... [14m41s elapsed]
time_sleep.wait_900_seconds: Still creating... [14m51s elapsed]
time_sleep.wait_900_seconds: Creation complete after 15m0s [id=2024-03-27T14:41:37Z]
local_file.GetZoneIDScript: Creating...
local_file.GetZoneIDScript: Creation complete after 0s [id=fe4217fc0b2887ee7d949cbbd15e3f338ce5ec13]
terraform_data.ZoneID: Creating...
terraform_data.ZoneID: Provisioning with 'local-exec'...
terraform_data.ZoneID (local-exec): Executing: ["PowerShell" "-File" "c:/temp/xdinst/DATA/GetZoneID.ps1"]
terraform_data.ZoneID: Creation complete after 1s [id=605cf1d3-df21-50fd-eea4-2b378bbc75de]
terraform_data.ZoneID2: Creating...
terraform_data.ZoneID2: Provisioning with 'local-exec'...
terraform_data.ZoneID2 (local-exec): Executing: ["PowerShell" "-File" "c:/temp/xdinst/DATA/GetZoneID.ps1"]
terraform_data.ZoneID2: Creation complete after 1s [id=8407eaeb-8b82-c81f-8f18-91e1c17c856e]
local_file.CreateValidCWCOnAVM: Creating...
local_file.CreateValidCWCOnAVM: Creation complete after 0s [id=8589dd5c009bc8ec60afe66f005270a5d6716790]
terraform_data.ExecuteCreateValidCWCOnAVM: Creating...
terraform_data.ExecuteCreateValidCWCOnAVM: Provisioning with 'local-exec'...
terraform_data.ExecuteCreateValidCWCOnAVM (local-exec): Executing: ["PowerShell" "-File" "c:/temp/xdinst/DATA/CreateValidCWCOnAVM.ps1"]
terraform_data.ExecuteCreateValidCWCOnAVM: Creation complete after 1s [id=fe29d6ab-b636-20c4-2546-36ee395f6ced]
local_file.RestartCC: Creating...
local_file.InstallCWCOnCC: Creating...
local_file.Log: Creating...
local_file.InstallCWCOnCC: Creation complete after 0s [id=83c2658962b3ca50411ecd6d7d3ab22958819dd1]
local_file.RestartCC: Creation complete after 0s [id=c33d06fc1d6b6fec7b65e720320d1cbc71f02225]
local_file.Log: Creation complete after 0s [id=d725ce92ca8335439a5d83acf47f3ca2c957a515]
local_file.LogData: Creating...
local_file.LogData: Creation complete after 0s [id=d725ce92ca8335439a5d83acf47f3ca2c957a515]
null_resource.UploadRequiredComponentsToCC1: Creating...
null_resource.UploadRequiredComponentsToCC1: Provisioning with 'file'...
null_resource.UploadRequiredComponentsToCC2: Creating...
null_resource.UploadRequiredComponentsToCC2: Provisioning with 'file'...
#< CLIXML
<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04"><Obj S="progress" RefId="0"><TN RefId="0"><T>System.Management.Automation.PSCustomObject</T><T>System.Object</T></TN><MS><I64 N="SourceId">1</I64><PR N="Record"><AV>Preparing modules for first use.</AV><AI>0</AI><Nil /><PI>-1</PI><PC>-1</PC><T>Completed</T><SR>-1</SR><SD> </SD></PR></MS></Obj></Objs>#< CLIXML
<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04"><Obj S="progress" RefId="0"><TN RefId="0"><T>System.Management.Automation.PSCustomObject</T><T>System.Object</T></TN><MS><I64 N="SourceId">1</I64><PR N="Record"><AV>Preparing modules for first use.</AV><AI>0</AI><Nil /><PI>-1</PI><PC>-1</PC><T>Completed</T><SR>-1</SR><SD> </SD></PR></MS></Obj></Objs>null_resource.UploadRequiredComponentsToCC1: Provisioning with 'file'...
#< CLIXML
<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04"><Obj S="progress" RefId="0"><TN RefId="0"><T>System.Management.Automation.PSCustomObject</T><T>System.Object</T></TN><MS><I64 N="SourceId">1</I64><PR N="Record"><AV>Preparing modules for first use.</AV><AI>0</AI><Nil /><PI>-1</PI><PC>-1</PC><T>Completed</T><SR>-1</SR><SD> </SD></PR></MS></Obj></Objs>#< CLIXML
<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04"><Obj S="progress" RefId="0"><TN RefId="0"><T>System.Management.Automation.PSCustomObject</T><T>System.Object</T></TN><MS><I64 N="SourceId">1</I64><PR N="Record"><AV>Preparing modules for first use.</AV><AI>0</AI><Nil /><PI>-1</PI><PC>-1</PC><T>Completed</T><SR>-1</SR><SD> </SD></PR></MS></Obj></Objs>#< CLIXML
<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04"><Obj S="progress" RefId="0"><TN RefId="0"><T>System.Management.Automation.PSCustomObject</T><T>System.Object</T></TN><MS><I64 N="SourceId">1</I64><PR N="Record"><AV>Preparing modules for first use.</AV><AI>0</AI><Nil /><PI>-1</PI><PC>-1</PC><T>Completed</T><SR>-1</SR><SD> </SD></PR></MS></Obj></Objs>null_resource.UploadRequiredComponentsToCC2: Provisioning with 'file'...
#< CLIXML
<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04"><Obj S="progress" RefId="0"><TN RefId="0"><T>System.Management.Automation.PSCustomObject</T><T>System.Object</T></TN><MS><I64 N="SourceId">1</I64><PR N="Record"><AV>Preparing modules for first use.</AV><AI>0</AI><Nil /><PI>-1</PI><PC>-1</PC><T>Completed</T><SR>-1</SR><SD> </SD></PR></MS></Obj></Objs>null_resource.UploadRequiredComponentsToCC1: Provisioning with 'file'...
#< CLIXML
<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04"><Obj S="progress" RefId="0"><TN RefId="0"><T>System.Management.Automation.PSCustomObject</T><T>System.Object</T></TN><MS><I64 N="SourceId">1</I64><PR N="Record"><AV>Preparing modules for first use.</AV><AI>0</AI><Nil /><PI>-1</PI><PC>-1</PC><T>Completed</T><SR>-1</SR><SD> </SD></PR></MS></Obj></Objs>#< CLIXML
<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04"><Obj S="progress" RefId="0"><TN RefId="0"><T>System.Management.Automation.PSCustomObject</T><T>System.Object</T></TN><MS><I64 N="SourceId">1</I64><PR N="Record"><AV>Preparing modules for first use.</AV><AI>0</AI><Nil /><PI>-1</PI><PC>-1</PC><T>Completed</T><SR>-1</SR><SD> </SD></PR></MS></Obj></Objs>#< CLIXML
<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04"><Obj S="progress" RefId="0"><TN RefId="0"><T>System.Management.Automation.PSCustomObject</T><T>System.Object</T></TN><MS><I64 N="SourceId">1</I64><PR N="Record"><AV>Preparing modules for first use.</AV><AI>0</AI><Nil /><PI>-1</PI><PC>-1</PC><T>Completed</T><SR>-1</SR><SD> </SD></PR></MS></Obj></Objs>null_resource.UploadRequiredComponentsToCC2: Provisioning with 'file'...
#< CLIXML
<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04"><Obj S="progress" RefId="0"><TN RefId="0"><T>System.Management.Automation.PSCustomObject</T><T>System.Object</T></TN><MS><I64 N="SourceId">1</I64><PR N="Record"><AV>Preparing modules for first use.</AV><AI>0</AI><Nil /><PI>-1</PI><PC>-1</PC><T>Completed</T><SR>-1</SR><SD> </SD></PR></MS></Obj></Objs>null_resource.UploadRequiredComponentsToCC1: Provisioning with 'file'...
#< CLIXML
<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04"><Obj S="progress" RefId="0"><TN RefId="0"><T>System.Management.Automation.PSCustomObject</T><T>System.Object</T></TN><MS><I64 N="SourceId">1</I64><PR N="Record"><AV>Preparing modules for first use.</AV><AI>0</AI><Nil /><PI>-1</PI><PC>-1</PC><T>Completed</T><SR>-1</SR><SD> </SD></PR></MS></Obj></Objs>#< CLIXML
<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04"><Obj S="progress" RefId="0"><TN RefId="0"><T>System.Management.Automation.PSCustomObject</T><T>System.Object</T></TN><MS><I64 N="SourceId">1</I64><PR N="Record"><AV>Preparing modules for first use.</AV><AI>0</AI><Nil /><PI>-1</PI><PC>-1</PC><T>Completed</T><SR>-1</SR><SD> </SD></PR></MS></Obj></Objs>#< CLIXML
<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04"><Obj S="progress" RefId="0"><TN RefId="0"><T>System.Management.Automation.PSCustomObject</T><T>System.Object</T></TN><MS><I64 N="SourceId">1</I64><PR N="Record"><AV>Preparing modules for first use.</AV><AI>0</AI><Nil /><PI>-1</PI><PC>-1</PC><T>Completed</T><SR>-1</SR><SD> </SD></PR></MS></Obj></Objs>null_resource.UploadRequiredComponentsToCC2: Provisioning with 'file'...
#< CLIXML
<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04"><Obj S="progress" RefId="0"><TN RefId="0"><T>System.Management.Automation.PSCustomObject</T><T>System.Object</T></TN><MS><I64 N="SourceId">1</I64><PR N="Record"><AV>Preparing modules for first use.</AV><AI>0</AI><Nil /><PI>-1</PI><PC>-1</PC><T>Completed</T><SR>-1</SR><SD> </SD></PR></MS></Obj></Objs>null_resource.UploadRequiredComponentsToCC1: Provisioning with 'file'...
null_resource.UploadRequiredComponentsToCC1: Still creating... [10s elapsed]
null_resource.UploadRequiredComponentsToCC2: Still creating... [10s elapsed]
#< CLIXML
<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04"><Obj S="progress" RefId="0"><TN RefId="0"><T>System.Management.Automation.PSCustomObject</T><T>System.Object</T></TN><MS><I64 N="SourceId">1</I64><PR N="Record"><AV>Preparing modules for first use.</AV><AI>0</AI><Nil /><PI>-1</PI><PC>-1</PC><T>Completed</T><SR>-1</SR><SD> </SD></PR></MS></Obj></Objs>#< CLIXML
<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04"><Obj S="progress" RefId="0"><TN RefId="0"><T>System.Management.Automation.PSCustomObject</T><T>System.Object</T></TN><MS><I64 N="SourceId">1</I64><PR N="Record"><AV>Preparing modules for first use.</AV><AI>0</AI><Nil /><PI>-1</PI><PC>-1</PC><T>Completed</T><SR>-1</SR><SD> </SD></PR></MS></Obj></Objs>#< CLIXML
<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04"><Obj S="progress" RefId="0"><TN RefId="0"><T>System.Management.Automation.PSCustomObject</T><T>System.Object</T></TN><MS><I64 N="SourceId">1</I64><PR N="Record"><AV>Preparing modules for first use.</AV><AI>0</AI><Nil /><PI>-1</PI><PC>-1</PC><T>Completed</T><SR>-1</SR><SD> </SD></PR></MS></Obj></Objs>null_resource.UploadRequiredComponentsToCC2: Provisioning with 'file'...
#< CLIXML
<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04"><Obj S="progress" RefId="0"><TN RefId="0"><T>System.Management.Automation.PSCustomObject</T><T>System.Object</T></TN><MS><I64 N="SourceId">1</I64><PR N="Record"><AV>Preparing modules for first use.</AV><AI>0</AI><Nil /><PI>-1</PI><PC>-1</PC><T>Completed</T><SR>-1</SR><SD> </SD></PR></MS></Obj></Objs>null_resource.UploadRequiredComponentsToCC1: Provisioning with 'file'...
#< CLIXML
<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04"><Obj S="progress" RefId="0"><TN RefId="0"><T>System.Management.Automation.PSCustomObject</T><T>System.Object</T></TN><MS><I64 N="SourceId">1</I64><PR N="Record"><AV>Preparing modules for first use.</AV><AI>0</AI><Nil /><PI>-1</PI><PC>-1</PC><T>Completed</T><SR>-1</SR><SD> </SD></PR></MS></Obj></Objs>#< CLIXML
<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04"><Obj S="progress" RefId="0"><TN RefId="0"><T>System.Management.Automation.PSCustomObject</T><T>System.Object</T></TN><MS><I64 N="SourceId">1</I64><PR N="Record"><AV>Preparing modules for first use.</AV><AI>0</AI><Nil /><PI>-1</PI><PC>-1</PC><T>Completed</T><SR>-1</SR><SD> </SD></PR></MS></Obj></Objs>#< CLIXML
<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04"><Obj S="progress" RefId="0"><TN RefId="0"><T>System.Management.Automation.PSCustomObject</T><T>System.Object</T></TN><MS><I64 N="SourceId">1</I64><PR N="Record"><AV>Preparing modules for first use.</AV><AI>0</AI><Nil /><PI>-1</PI><PC>-1</PC><T>Completed</T><SR>-1</SR><SD> </SD></PR></MS></Obj></Objs>null_resource.UploadRequiredComponentsToCC2: Provisioning with 'file'...
#< CLIXML
<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04"><Obj S="progress" RefId="0"><TN RefId="0"><T>System.Management.Automation.PSCustomObject</T><T>System.Object</T></TN><MS><I64 N="SourceId">1</I64><PR N="Record"><AV>Preparing modules for first use.</AV><AI>0</AI><Nil /><PI>-1</PI><PC>-1</PC><T>Completed</T><SR>-1</SR><SD> </SD></PR></MS></Obj></Objs>null_resource.UploadRequiredComponentsToCC1: Creation complete after 13s [id=3185822947916041730]
null_resource.CallRequiredScriptsOnCC1: Creating...
null_resource.CallRequiredScriptsOnCC1: Provisioning with 'remote-exec'...
null_resource.CallRequiredScriptsOnCC1 (remote-exec): Connecting to remote host via WinRM...
null_resource.CallRequiredScriptsOnCC1 (remote-exec):   Host: 10.156.0.5
null_resource.CallRequiredScriptsOnCC1 (remote-exec):   Port: 5985
null_resource.CallRequiredScriptsOnCC1 (remote-exec):   User: administrator
null_resource.CallRequiredScriptsOnCC1 (remote-exec):   Password: true
null_resource.CallRequiredScriptsOnCC1 (remote-exec):   HTTPS: false
null_resource.CallRequiredScriptsOnCC1 (remote-exec):   Insecure: false
null_resource.CallRequiredScriptsOnCC1 (remote-exec):   NTLM: false
null_resource.CallRequiredScriptsOnCC1 (remote-exec):   CACert: false
null_resource.CallRequiredScriptsOnCC1 (remote-exec): Connected!
#< CLIXML
<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04"><Obj S="progress" RefId="0"><TN RefId="0"><T>System.Management.Automation.PSCustomObject</T><T>System.Object</T></TN><MS><I64 N="SourceId">1</I64><PR N="Record"><AV>Preparing modules for first use.</AV><AI>0</AI><Nil /><PI>-1</PI><PC>-1</PC><T>Completed</T><SR>-1</SR><SD> </SD></PR></MS></Obj></Objs>#< CLIXML
<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04"><Obj S="progress" RefId="0"><TN RefId="0"><T>System.Management.Automation.PSCustomObject</T><T>System.Object</T></TN><MS><I64 N="SourceId">1</I64><PR N="Record"><AV>Preparing modules for first use.</AV><AI>0</AI><Nil /><PI>-1</PI><PC>-1</PC><T>Completed</T><SR>-1</SR><SD> </SD></PR></MS></Obj></Objs>#< CLIXML
<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04"><Obj S="progress" RefId="0"><TN RefId="0"><T>System.Management.Automation.PSCustomObject</T><T>System.Object</T></TN><MS><I64 N="SourceId">1</I64><PR N="Record"><AV>Preparing modules for first use.</AV><AI>0</AI><Nil /><PI>-1</PI><PC>-1</PC><T>Completed</T><SR>-1</SR><SD> </SD></PR></MS></Obj></Objs>null_resource.UploadRequiredComponentsToCC2: Creation complete after 15s [id=979308782134187951]
null_resource.CallRequiredScriptsOnCC2: Creating...
null_resource.CallRequiredScriptsOnCC2: Provisioning with 'remote-exec'...
null_resource.CallRequiredScriptsOnCC2 (remote-exec): Connecting to remote host via WinRM...
null_resource.CallRequiredScriptsOnCC2 (remote-exec):   Host: 10.156.0.6
null_resource.CallRequiredScriptsOnCC2 (remote-exec):   Port: 5985
null_resource.CallRequiredScriptsOnCC2 (remote-exec):   User: administrator
null_resource.CallRequiredScriptsOnCC2 (remote-exec):   Password: true
null_resource.CallRequiredScriptsOnCC2 (remote-exec):   HTTPS: false
null_resource.CallRequiredScriptsOnCC2 (remote-exec):   Insecure: false
null_resource.CallRequiredScriptsOnCC2 (remote-exec):   NTLM: false
null_resource.CallRequiredScriptsOnCC2 (remote-exec):   CACert: false
null_resource.CallRequiredScriptsOnCC2 (remote-exec): Connected!
#< CLIXML
<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04"><Obj S="progress" RefId="0"><TN RefId="0"><T>System.Management.Automation.PSCustomObject</T><T>System.Object</T></TN><MS><I64 N="SourceId">1</I64><PR N="Record"><AV>Preparing modules for first use.</AV><AI>0</AI><Nil /><PI>-1</PI><PC>-1</PC><T>Completed</T><SR>-1</SR><SD> </SD></PR></MS></Obj></Objs>#< CLIXML
<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04"><Obj S="progress" RefId="0"><TN RefId="0"><T>System.Management.Automation.PSCustomObject</T><T>System.Object</T></TN><MS><I64 N="SourceId">1</I64><PR N="Record"><AV>Preparing modules for first use.</AV><AI>0</AI><Nil /><PI>-1</PI><PC>-1</PC><T>Completed</T><SR>-1</SR><SD> </SD></PR></MS></Obj></Objs>
null_resource.CallRequiredScriptsOnCC1 (remote-exec): C:\Users\Administrator>powershell -File c:/temp/xdinst/DATA/InstallCWCOnCC.ps1
#< CLIXML
<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04"><Obj S="progress" RefId="0"><TN RefId="0"><T>System.Management.Automation.PSCustomObject</T><T>System.Object</T></TN><MS><I64 N="SourceId">1</I64><PR N="Record"><AV>Preparing modules for first use.</AV><AI>0</AI><Nil /><PI>-1</PI><PC>-1</PC><T>Completed</T><SR>-1</SR><SD> </SD></PR></MS></Obj></Objs>
null_resource.CallRequiredScriptsOnCC2 (remote-exec): C:\Users\Administrator.TACG-GCP-TF-CC2>powershell -File c:/temp/xdinst/DATA/InstallCWCOnCC.ps1
null_resource.CallRequiredScriptsOnCC1: Still creating... [10s elapsed]
null_resource.CallRequiredScriptsOnCC2: Still creating... [10s elapsed]
null_resource.CallRequiredScriptsOnCC1: Still creating... [20s elapsed]
null_resource.CallRequiredScriptsOnCC2: Still creating... [20s elapsed]
null_resource.CallRequiredScriptsOnCC1: Still creating... [30s elapsed]
null_resource.CallRequiredScriptsOnCC2: Still creating... [30s elapsed]
null_resource.CallRequiredScriptsOnCC1: Still creating... [40s elapsed]
null_resource.CallRequiredScriptsOnCC2: Still creating... [40s elapsed]
null_resource.CallRequiredScriptsOnCC1: Still creating... [50s elapsed]
null_resource.CallRequiredScriptsOnCC2: Still creating... [50s elapsed]
null_resource.CallRequiredScriptsOnCC1: Still creating... [1m0s elapsed]
null_resource.CallRequiredScriptsOnCC2: Still creating... [1m0s elapsed]
null_resource.CallRequiredScriptsOnCC1: Still creating... [1m10s elapsed]
null_resource.CallRequiredScriptsOnCC2: Still creating... [1m10s elapsed]
null_resource.CallRequiredScriptsOnCC1: Still creating... [1m20s elapsed]
null_resource.CallRequiredScriptsOnCC2: Still creating... [1m20s elapsed]
null_resource.CallRequiredScriptsOnCC1: Still creating... [1m30s elapsed]
null_resource.CallRequiredScriptsOnCC2: Still creating... [1m30s elapsed]
#< CLIXML
<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04"><Obj S="progress" RefId="0"><TN RefId="0"><T>System.Management.Automation.PSCustomObject</T><T>System.Object</T></TN><MS><I64 N="SourceId">1</I64><PR N="Record"><AV>Preparing modules for first use.</AV><AI>0</AI><Nil /><PI>-1</PI><PC>-1</PC><T>Completed</T><SR>-1</SR><SD> </SD></PR></MS></Obj><Obj S="progress" RefId="1"><TNRef RefId="0" /><MS><I64 N="SourceId">1</I64><PR N="Record"><AV>Preparing modules for first use.</AV><AI>0</AI><Nil /><PI>-1</PI><PC>-1</PC><T>Completed</T><SR>-1</SR><SD> </SD></PR></MS></Obj></Objs>null_resource.CallRequiredScriptsOnCC1: Still creating... [1m40s elapsed]
#< CLIXML
<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04"><Obj S="progress" RefId="0"><TN RefId="0"><T>System.Management.Automation.PSCustomObject</T><T>System.Object</T></TN><MS><I64 N="SourceId">1</I64><PR N="Record"><AV>Preparing modules for first use.</AV><AI>0</AI><Nil /><PI>-1</PI><PC>-1</PC><T>Completed</T><SR>-1</SR><SD> </SD></PR></MS></Obj><Obj S="progress" RefId="1"><TNRef RefId="0" /><MS><I64 N="SourceId">1</I64><PR N="Record"><AV>Preparing modules for first use.</AV><AI>0</AI><Nil /><PI>-1</PI><PC>-1</PC><T>Completed</T><SR>-1</SR><SD> </SD></PR></MS></Obj></Objs>#< CLIXML
<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04"><Obj S="progress" RefId="0"><TN RefId="0"><T>System.Management.Automation.PSCustomObject</T><T>System.Object</T></TN><MS><I64 N="SourceId">1</I64><PR N="Record"><AV>Preparing modules for first use.</AV><AI>0</AI><Nil /><PI>-1</PI><PC>-1</PC><T>Completed</T><SR>-1</SR><SD> </SD></PR></MS></Obj></Objs>null_resource.CallRequiredScriptsOnCC1: Creation complete after 1m41s [id=1683817289421392956]
time_sleep.wait_1800_seconds_CC1: Creating...
#< CLIXML
<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04"><Obj S="progress" RefId="0"><TN RefId="0"><T>System.Management.Automation.PSCustomObject</T><T>System.Object</T></TN><MS><I64 N="SourceId">1</I64><PR N="Record"><AV>Preparing modules for first use.</AV><AI>0</AI><Nil /><PI>-1</PI><PC>-1</PC><T>Completed</T><SR>-1</SR><SD> </SD></PR></MS></Obj></Objs>null_resource.CallRequiredScriptsOnCC2: Creation complete after 1m39s [id=5495494345692724821]
time_sleep.wait_1800_seconds_CC2: Creating...
time_sleep.wait_1800_seconds_CC1: Still creating... [10s elapsed]
time_sleep.wait_1800_seconds_CC2: Still creating... [11s elapsed]
time_sleep.wait_1800_seconds_CC1: Still creating... [20s elapsed]
time_sleep.wait_1800_seconds_CC2: Still creating... [21s elapsed]
time_sleep.wait_1800_seconds_CC1: Still creating... [30s elapsed]
time_sleep.wait_1800_seconds_CC2: Still creating... [31s elapsed]
time_sleep.wait_1800_seconds_CC1: Still creating... [40s elapsed]
time_sleep.wait_1800_seconds_CC2: Still creating... [41s elapsed]
time_sleep.wait_1800_seconds_CC1: Still creating... [50s elapsed]
time_sleep.wait_1800_seconds_CC2: Still creating... [51s elapsed]

... ** Output shortened ** ... 

time_sleep.wait_1800_seconds_CC2: Still creating... [29m42s elapsed]
time_sleep.wait_1800_seconds_CC1: Still creating... [29m52s elapsed]
time_sleep.wait_1800_seconds_CC2: Still creating... [29m52s elapsed]
time_sleep.wait_1800_seconds_CC1: Creation complete after 30m0s [id=2024-03-28T10:41:32Z]
null_resource.CallRebootScriptOnCC1: Creating...
null_resource.CallRebootScriptOnCC1: Provisioning with 'remote-exec'...
null_resource.CallRebootScriptOnCC1 (remote-exec): Connecting to remote host via WinRM...
null_resource.CallRebootScriptOnCC1 (remote-exec):   Host: 10.156.0.5
null_resource.CallRebootScriptOnCC1 (remote-exec):   Port: 5985
null_resource.CallRebootScriptOnCC1 (remote-exec):   User: administrator
null_resource.CallRebootScriptOnCC1 (remote-exec):   Password: true
null_resource.CallRebootScriptOnCC1 (remote-exec):   HTTPS: false
null_resource.CallRebootScriptOnCC1 (remote-exec):   Insecure: false
null_resource.CallRebootScriptOnCC1 (remote-exec):   NTLM: false
null_resource.CallRebootScriptOnCC1 (remote-exec):   CACert: false
time_sleep.wait_1800_seconds_CC2: Creation complete after 30m1s [id=2024-03-28T10:41:32Z]
null_resource.CallRebootScriptOnCC2: Creating...
null_resource.CallRebootScriptOnCC2: Provisioning with 'remote-exec'...
null_resource.CallRebootScriptOnCC2 (remote-exec): Connecting to remote host via WinRM...
null_resource.CallRebootScriptOnCC2 (remote-exec):   Host: 10.156.0.6
null_resource.CallRebootScriptOnCC2 (remote-exec):   Port: 5985
null_resource.CallRebootScriptOnCC2 (remote-exec):   User: administrator
null_resource.CallRebootScriptOnCC2 (remote-exec):   Password: true
null_resource.CallRebootScriptOnCC2 (remote-exec):   HTTPS: false
null_resource.CallRebootScriptOnCC2 (remote-exec):   Insecure: false
null_resource.CallRebootScriptOnCC2 (remote-exec):   NTLM: false
null_resource.CallRebootScriptOnCC2 (remote-exec):   CACert: false
null_resource.CallRebootScriptOnCC1 (remote-exec): Connected!
null_resource.CallRebootScriptOnCC2 (remote-exec): Connected!
#< CLIXML
<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04"><Obj S="progress" RefId="0"><TN RefId="0"><T>System.Management.Automation.PSCustomObject</T><T>System.Object</T></TN><MS><I64 N="SourceId">1</I64><PR N="Record"><AV>Preparing modules for first use.</AV><AI>0</AI><Nil /><PI>-1</PI><PC>-1</PC><T>Completed</T><SR>-1</SR><SD> </SD></PR></MS></Obj></Objs>#< CLIXML
<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04"><Obj S="progress" RefId="0"><TN RefId="0"><T>System.Management.Automation.PSCustomObject</T><T>System.Object</T></TN><MS><I64 N="SourceId">1</I64><PR N="Record"><AV>Preparing modules for first use.</AV><AI>0</AI><Nil /><PI>-1</PI><PC>-1</PC><T>Completed</T><SR>-1</SR><SD> </SD></PR></MS></Obj></Objs>#< CLIXML
<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04"><Obj S="progress" RefId="0"><TN RefId="0"><T>System.Management.Automation.PSCustomObject</T><T>System.Object</T></TN><MS><I64 N="SourceId">1</I64><PR N="Record"><AV>Preparing modules for first use.</AV><AI>0</AI><Nil /><PI>-1</PI><PC>-1</PC><T>Completed</T><SR>-1</SR><SD> </SD></PR></MS></Obj></Objs>
null_resource.CallRebootScriptOnCC1 (remote-exec): C:\Users\Administrator>powershell -File c:/temp/xdinst/DATA/RebootCC.ps1
null_resource.CallRebootScriptOnCC1 (remote-exec): Windows PowerShell
null_resource.CallRebootScriptOnCC1 (remote-exec): Copyright (C) Microsoft Corporation. All rights reserved.
null_resource.CallRebootScriptOnCC1 (remote-exec): Install the latest PowerShell for new features and improvements! https://aka.ms/PSWindows
#< CLIXML
<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04"><Obj S="progress" RefId="0"><TN RefId="0"><T>System.Management.Automation.PSCustomObject</T><T>System.Object</T></TN><MS><I64 N="SourceId">1</I64><PR N="Record"><AV>Preparing modules for first use.</AV><AI>0</AI><Nil /><PI>-1</PI><PC>-1</PC><T>Completed</T><SR>-1</SR><SD> </SD></PR></MS></Obj></Objs>null_resource.CallRebootScriptOnCC2: Still creating... [10s elapsed]

... ** Output shortened ** ... 

Apply complete! Resources: 30 added, 0 changed, 0 destroyed.
PS C:\TACG\_CCOnGCP\_CCOnGCP-Install>

This configuration completes the creation and configuration of all initial resources:

  • Installing the needed software on the Cloud Connectors
  • Creating a Resource Location in Citrix Cloud
  • Configuring the 2 Cloud Connectors
  • Registering the 2 Cloud Connectors in the newly created Resource Location

After successful runs of all needed scripts, we can see the new Resource Location in Citrix Cloud and the two Cloud Connectors bound to the Resource Location:

cwc1-ok.png

cwc2-ok.png

cc-rl-ok.png

 

The environment is now ready to deploy a Machine Catalog and a Delivery Group using Module 3.

Module 3: Create all Resources in Google Cloud Platform (GCP) and Citrix Cloud

This module is split into the following configuration parts:

  1. Creating a Hypervisor Connection to Google Cloud Platform (GCP) and a Hypervisor Pool
  2. Creating a Machine Catalog (MC) in the newly created Resource Location
  3. Creating a Delivery Group (DG) based on the MC in the newly created Resource Location
  4. Deploying some example policies using Terraform

The Terraform configuration contains some idle time slots to ensure that background operations on Google Cloud Platform (GCP) or on the VMs can be completed before the next configuration steps occur.
We have seen different elapsed configuration times related to different loads on the Google Cloud Platform (GCP) systems! Before Terraform can create the Hypervisor Connection and the Hypervisor Pool, Terraform must retrieve the Site-ID and Zone-ID of the newly created Resource Location. As the Citrix Terraform Provider currently has no Cloud-level functionalities implemented, Terraform needs PowerShell scripts to retrieve the IDs. It created the necessary scripts with all the needed variables, saved the scripts, and executed them in Module 2.

NOTE: The Citrix Remote PowerShell SDK must be installed on the machine running Terraform as Terraform relies on the SDK!

After retrieving the IDs, Terraform configures a Hypervisor Connection to Google Cloud Platform (GCP) and a Hypervisor Resource Pool associated with the Hypervisor Connection. As soon as these prerequisites are completed, the Machine Catalog is created. After successfully creating the Hypervisor Connection, the Hypervisor Resource Pool, and the Machine Catalog, the last step of the deployment process starts - creating the Delivery Group.

The Terraform configuration assumes that all machines in the created Machine Catalog are used in the Delivery Group and that Autoscale will be configured for this Delivery Group.
More information about Autoscale can be found here: https://docs.citrix.com/en-us/tech-zone/learn/tech-briefs/autoscale.html

NOTE: This module has some complexity as many automated actions and many different entities are created. The entities can be configured using the corresponding .auto.tfvars.json file.

The deployment of Citrix Policies is a new feature built in version 0.5.2. We need to know the internal policy name, as localized policy names and descriptions are unusable.
Therefore, we need to use a PowerShell script to determine all internal names - some prerequisites are necessary for the script to work.


You can use any machine but the Cloud Connectors!

After installation of the pre-requisites open a PowerShell console:

Import-Module "C:\TACG\Supportability Pack\Tools\Scout\Current\Utilities\Citrix.GroupPolicy.commands.psm1" -force
new-psdrive -name LocalFarmGpo -psprovider CitrixGroupPolicy -controller localhost \
Get-PSDrive
cd LocalFarmGpo:
Get-CtxGroupPolicyConfiguration 

Type:  User
ProfileLoadTimeMonitoring_Threshold      
ICALatencyMonitoring_Enable              
ICALatencyMonitoring_Period              
ICALatencyMonitoring_Threshold           
EnableLossless                           
ProGraphics                              
FRVideos_Part                            
FRVideosPath_Part                        
FRStartMenu_Part                         
FRStartMenuPath_Part                     
FRSearches_Part                          
FRSearchesPath_Part                      
FRSavedGames_Part                        
FRSavedGamesPath_Part                    
FRPictures_Part                          
FRPicturesPath_Part                      
FRMusic_Part                             
FRMusicPath_Part                         
FRLinks_Part                             
FRLinksPath_Part                         
FRFavorites_Part                         
FRFavoritesPath_Part                     
FRDownloads_Part                         
FRDownloadsPath_Part                     
FRDocuments_Part                         
FRDocumentsPath_Part                     
FRDesktop_Part                           
FRDesktopPath_Part                       
FRContacts_Part                          
FRContactsPath_Part                      
FRAdminAccess_Part                       
FRIncDomainName_Part                     
FRAppData_Part                           
FRAppDataPath_Part                       
StorefrontAccountsList                   
AllowFidoRedirection                     
AllowWIARedirection                      
ClientClipboardWriteAllowedFormats       
ClipboardRedirection                     
ClipboardSelectionUpdateMode             
DesktopLaunchForNonAdmins                
DragDrop                                 
LimitClipboardTransferC2H                
LimitClipboardTransferH2C                
LossTolerantModeAvailable                
NonPublishedProgramLaunching             
PrimarySelectionUpdateMode               
ReadonlyClipboard                        
RestrictClientClipboardWrite             
RestrictSessionClipboardWrite            
SessionClipboardWriteAllowedFormats      
FramesPerSecond                          
PreferredColorDepthForSimpleGraphics     
VisualQuality                            
ExtraColorCompression                    
ExtraColorCompressionThreshold           
LossyCompressionLevel                    
LossyCompressionThreshold                
ProgressiveHeavyweightCompression        
MinimumAdaptiveDisplayJpegQuality        
MovingImageCompressionConfiguration      
ProgressiveCompressionLevel              
ProgressiveCompressionThreshold          
TargetedMinimumFramesPerSecond           
ClientUsbDeviceOptimizationRules         
UsbConnectExistingDevices                
UsbConnectNewDevices                     
UsbDeviceRedirection                     
UsbDeviceRedirectionRules                
USBDeviceRulesV2                         
UsbPlugAndPlayRedirection                
TwainCompressionLevel                    
TwainRedirection                         
LocalTimeEstimation                      
RestoreServerTime                        
SessionTimeZone                          
EnableSessionWatermark                   
WatermarkStyle                           
WatermarkTransparency                    
WatermarkCustomText                      
WatermarkIncludeClientIPAddress          
WatermarkIncludeConnectTime              
WatermarkIncludeLogonUsername            
WatermarkIncludeVDAHostName              
WatermarkIncludeVDAIPAddress             
EnableRemotePCDisconnectTimer            
SessionConnectionTimer                   
SessionConnectionTimerInterval           
SessionDisconnectTimer                   
SessionDisconnectTimerInterval           
SessionIdleTimer                         
SessionIdleTimerInterval                 
LossTolerantThresholds                   
EnableServerConnectionTimer              
EnableServerDisconnectionTimer           
EnableServerIdleTimer                    
ServerConnectionTimerInterval            
ServerDisconnectionTimerInterval         
ServerIdleTimerInterval                  
MinimumEncryptionLevel                   
AutoCreationEventLogPreference           
ClientPrinterRedirection                 
DefaultClientPrinter                     
PrinterAssignments                       
SessionPrinters                          
WaitForPrintersToBeCreated               
UpsPrintStreamInputBandwidthLimit        
DPILimit                                 
EMFProcessingMode                        
ImageCompressionLimit                    
UniversalPrintingPreviewPreference       
UPDCompressionDefaults                                        
InboxDriverAutoInstallation              
UniversalDriverPriority                  
UniversalPrintDriverUsage                
AutoCreatePDFPrinter                     
ClientPrinterAutoCreation                
ClientPrinterNames                       
DirectConnectionsToPrintServers          
GenericUniversalPrinterAutoCreation      
PrinterDriverMappings                    
PrinterPropertiesRetention               
ClientComPortRedirection                 
ClientComPortsAutoConnection             
ClientLptPortRedirection                 
ClientLptPortsAutoConnection             
MaxSpeexQuality                          
MSTeamsRedirection                       
MultimediaOptimization                   
UseGPUForMultimediaOptimization          
VideoLoadManagement                      
VideoQuality                             
WebBrowserRedirectionAcl                 
WebBrowserRedirectionAuthenticationSites 
WebBrowserRedirectionBlacklist           
WebBrowserRedirectionIwaSupport          
WebBrowserRedirectionProxy               
WebBrowserRedirectionProxyAuth           
MultiStream                              
AutoKeyboardPopUp                        
ComboboxRemoting                         
MobileDesktop                            
TabletModeToggle                         
ClientKeyboardLayoutSyncAndIME           
EnableUnicodeKeyboardLayoutMapping       
HideKeyboardLayoutSwitchPopupMessageBox  
AllowVisuallyLosslessCompression         
DisplayLosslessIndicator                 
OptimizeFor3dWorkload                    
ScreenSharing                            
UseHardwareEncodingForVideoCodec         
UseVideoCodecForCompression              
EnableFramehawkDisplayChannel            
AllowFileDownload                        
AllowFileTransfer                        
AllowFileUpload                          
AsynchronousWrites                       
AutoConnectDrives                        
ClientDriveLetterPreservation            
ClientDriveRedirection                   
ClientFixedDrives                        
ClientFloppyDrives                       
ClientNetworkDrives                      
ClientOpticalDrives                      
ClientRemoveableDrives                   
HostToClientRedirection                  
ReadOnlyMappedDrive                      
SpecialFolderRedirection                 
AeroRedirection                          
DesktopWallpaper                         
GraphicsQuality                          
MenuAnimation                            
WindowContentsVisibleWhileDragging       
AllowLocationServices                    
AllowBidirectionalContentRedirection     
BidirectionalRedirectionConfig           
ClientURLs                               
VDAURLs                                  
AudioBandwidthLimit                      
AudioBandwidthPercent                    
ClipboardBandwidthLimit                  
ClipboardBandwidthPercent                
ComPortBandwidthLimit                    
ComPortBandwidthPercent                  
FileRedirectionBandwidthLimit            
FileRedirectionBandwidthPercent          
HDXMultimediaBandwidthLimit              
HDXMultimediaBandwidthPercent            
LptBandwidthLimit                        
LptBandwidthLimitPercent                 
OverallBandwidthLimit                    
PrinterBandwidthLimit                    
PrinterBandwidthPercent                  
TwainBandwidthLimit                      
TwainBandwidthPercent                    
USBBandwidthLimit                        
USBBandwidthPercent                      
AllowRtpAudio                            
AudioPlugNPlay                           
AudioQuality                             
ClientAudioRedirection                   
EnableAdaptiveAudio                      
MicrophoneRedirection                    
FlashAcceleration                        
FlashBackwardsCompatibility              
FlashDefaultBehavior                     
FlashEventLogging                        
FlashIntelligentFallback                 
FlashLatencyThreshold                    
FlashServerSideContentFetchingWhitelist  
FlashUrlColorList                        
FlashUrlCompatibilityList                
HDXFlashLoadManagement                   
HDXFlashLoadManagementErrorSwf           

Type:  Computer
WemCloudConnectorList                        
VirtualLoopbackPrograms                      
VirtualLoopbackSupport                       
EnableAutoUpdateOfControllers                
AppFailureExclusionList                      
EnableProcessMonitoring                      
EnableResourceMonitoring                     
EnableWorkstationVDAFaultMonitoring          
SelectedFailureLevel                         
CPUUsageMonitoring_Enable                    
CPUUsageMonitoring_Period                    
CPUUsageMonitoring_Threshold                 
VdcPolicyEnable                              
EnableClipboardMetadataCollection            
EnableVdaDiagnosticsCollection               
XenAppOptimizationDefinitionPathData         
XenAppOptimizationEnabled                    
ExclusionList_Part                           
IncludeListRegistry_Part                     
LastKnownGoodRegistry                        
DefaultExclusionList                         
ExclusionDefaultReg01                        
ExclusionDefaultReg02                        
ExclusionDefaultReg03                        
PSAlwaysCache                                
PSAlwaysCache_Part                           
PSEnabled                                    
PSForFoldersEnabled                          
PSForPendingAreaEnabled                      
PSPendingLockTimeout                         
PSUserGroups_Part                            
StreamingExclusionList_Part                  
ApplicationProfilesAutoMigration             
DeleteCachedProfilesOnLogoff                 
LocalProfileConflictHandling_Part            
MigrateWindowsProfilesToUserStore_Part       
ProfileDeleteDelay_Part                      
TemplateProfileIsMandatory                   
TemplateProfileOverridesLocalProfile         
TemplateProfileOverridesRoamingProfile       
TemplateProfilePath                          
DisableConcurrentAccessToOneDriveContainer   
DisableConcurrentAccessToProfileContainer    
EnableVHDAutoExtend                          
EnableVHDDiskCompaction                      
GroupsToAccessProfileContainer_Part          
PreventLoginWhenMountFailed_Part             
ProfileContainerExclusionListDir_Part        
ProfileContainerExclusionListFile_Part       
ProfileContainerInclusionListDir_Part        
ProfileContainerInclusionListFile_Part       
ProfileContainerLocalCache                   
DebugFilePath_Part                           
DebugMode                                    
LogLevel_ActiveDirectoryActions              
LogLevel_FileSystemActions                   
LogLevel_FileSystemNotification              
LogLevel_Information                         
LogLevel_Logoff                              
LogLevel_Logon                               
LogLevel_PolicyUserLogon                     
LogLevel_RegistryActions                     
LogLevel_RegistryDifference                  
LogLevel_UserName                            
LogLevel_Warnings                            
MaxLogSize_Part                              
LargeFileHandlingList_Part                   
LogonExclusionCheck_Part                     
AccelerateFolderMirroring                    
MirrorFoldersList_Part                       
ProfileContainer_Part                        
SyncDirList_Part                             
SyncFileList_Part                            
ExclusionListSyncDir_Part                    
ExclusionListSyncFiles_Part                  
DefaultExclusionListSyncDir                  
ExclusionDefaultDir01                        
ExclusionDefaultDir02                        
ExclusionDefaultDir03                        
ExclusionDefaultDir04                        
ExclusionDefaultDir05                        
ExclusionDefaultDir06                        
ExclusionDefaultDir07                        
ExclusionDefaultDir08                        
ExclusionDefaultDir09                        
ExclusionDefaultDir10                        
ExclusionDefaultDir11                        
ExclusionDefaultDir12                        
ExclusionDefaultDir13                        
ExclusionDefaultDir14                        
ExclusionDefaultDir15                        
ExclusionDefaultDir16                        
ExclusionDefaultDir17                        
ExclusionDefaultDir18                        
ExclusionDefaultDir19                        
ExclusionDefaultDir20                        
ExclusionDefaultDir21                        
ExclusionDefaultDir22                        
ExclusionDefaultDir23                        
ExclusionDefaultDir24                        
ExclusionDefaultDir25                        
ExclusionDefaultDir26                        
ExclusionDefaultDir27                        
ExclusionDefaultDir28                        
ExclusionDefaultDir29                        
ExclusionDefaultDir30                        
SharedStoreFileExclusionList_Part            
SharedStoreFileInclusionList_Part            
SharedStoreProfileContainerFileSizeLimit_Part
CPEnable                                     
CPMigrationFromBaseProfileToCPStore          
CPPathData                                   
CPSchemaPathData                             
CPUserGroups_Part                            
DATPath_Part                                 
ExcludedGroups_Part                          
MigrateUserStore_Part                        
OfflineSupport                               
ProcessAdmins                                
ProcessedGroups_Part                         
PSMidSessionWriteBack                        
PSMidSessionWriteBackReg                     
PSMidSessionWriteBackSessionLock             
ServiceActive                                
AppAccessControl_Part                        
CEIPEnabled                                  
CredBasedAccessEnabled                       
DisableDynamicConfig                         
EnableVolumeReattach                         
FreeRatio4Compaction_Part                    
FSLogixProfileContainerSupport               
LoadRetries_Part                             
LogoffRatherThanTempProfile                  
MultiSiteReplication_Part                    
NDefrag4Compaction                           
NLogoffs4Compaction_Part                     
OneDriveContainer_Part                       
OrderedGroups_Part                           
OutlookEdbBackupEnabled                      
OutlookSearchRoamingConcurrentSession        
OutlookSearchRoamingConcurrentSession_Part   
OutlookSearchRoamingEnabled                  
ProcessCookieFiles                           
SyncGpoStateEnabled                          
UserGroupLevelConfigEnabled                  
UserStoreSelection_Part                      
UwpAppsRoaming                               
VhdAutoExpansionIncrement_Part               
VhdAutoExpansionLimit_Part                   
VhdAutoExpansionThreshold_Part               
VhdContainerCapacity_Part                    
VhdStorePath_Part                            
UplCustomizedUserLayerSizeInGb               
UplGroupsUsingCustomizedUserLayerSize        
UplRepositoryPath                            
UplUserExclusions                            
UplUserLayerSizeInGb                         
ConcurrentLogonsTolerance                    
CPUUsage                                     
CPUUsageExcludedProcessPriority              
DiskUsage                                    
MaximumNumberOfSessions                      
MemoryUsage                                  
MemoryUsageBaseLoad                          
ApplicationLaunchWaitTimeout                 
HDXAdaptiveTransport                         
HDXDirect                                    
HDXDirectMode                                
HDXDirectPortRange                           
IcaListenerPortNumber                        
IcaListenerTimeout                           
LogoffCheckerStartupDelay                    
RemoteCredentialGuard                        
RendezvousProtocol                           
RendezvousProxy                              
SecureHDX                                    
VdaUpgradeProxy                              
VirtualChannelWhiteList                      
VirtualChannelWhiteListLogging               
VirtualChannelWhiteListLogThrottling         
AcceptWebSocketsConnections                  
WebSocketsPort                               
WSTrustedOriginServerList                    
SessionReliabilityConnections                
SessionReliabilityPort                       
SessionReliabilityTimeout                    
IdleTimerInterval                            
LoadBalancedPrintServers                     
PrintServersOutOfServiceThreshold            
UpcHttpConnectTimeout                        
UpcHttpReceiveTimeout                        
UpcHttpSendTimeout                           
UpcSslCgpPort                                
UpcSslCipherSuite                            
UpcSslComplianceMode                         
UpcSslEnable                                 
UpcSslFips                                   
UpcSslHttpsPort                              
UpcSslProtocolVersion                        
UpsCgpPort                                   
UpsEnable                                    
UpsHttpPort                                  
HTML5VideoRedirection                        
MultimediaAcceleration                       
MultimediaAccelerationDefaultBufferSize      
MultimediaAccelerationEnableCSF              
MultimediaAccelerationUseDefaultBufferSize   
MultimediaConferencing                       
WebBrowserRedirection                        
MultiPortPolicy                              
MultiStreamAssignment                                                          
MultiStreamPolicy                            
RtpAudioPortRange                            
UDPAudioOnServer                             
AllowLocalAppAccess                          
URLRedirectionBlackList                      
URLRedirectionWhiteList                      
IcaKeepAlives                                
IcaKeepAliveTimeout                          
DisplayDegradePreference                     
DisplayDegradeUserNotification               
DisplayMemoryLimit                           
DynamicPreview                               
ImageCaching                                 
LegacyGraphicsMode                           
MaximumColorDepth                            
QueueingAndTossing                           
FramehawkDisplayChannelPortRange             
PersistentCache                              
EnhancedDesktopExperience                    
IcaRoundTripCalculation                      
IcaRoundTripCalculationInterval              
IcaRoundTripCalculationWhenIdle              
ACRTimeout                                   
AutoClientReconnect                          
AutoClientReconnectAuthenticationRequired    
AutoClientReconnectLogging                   
ReconnectionUiTransparencyLevel              
AppProtectionPostureCheck                    
AdvanceWarningFrequency                      
AdvanceWarningMessageTitle                   
AdvanceWarningPeriod                         
AgentTaskInterval                            
FinalForceLogoffMessageBody                  
FinalForceLogoffMessageTitle                 
ForceLogoffGracePeriod                       
ForceLogoffMessageTitle                      
ImageProviderIntegrationEnabled              
RebootMessageBody

Using these names allows the deployment of policies by using the Terraform provider.

NOTE: Currently it is only supported to deploy policies using Terraform on-premise with CVAD 2402 or higher. Trying to deploy policies on a DaaS-based environment does not work yet, as the needed backend configurations are not generally available yet. We will update this guide as soon as the Cloud backend has all modifications generally rolled out.

Please make sure you have configured the variables according to your needs.

Caution:

Be sure that your subscription has no quota limitation on the chosen VM type and you have enough resources on Google Cloud Platform (GCP) to create all the Virtual Machines planned to put into the Machine Catalog by checking quotas - otherwise the creation of the Machine Catalog will fail if there are not enough compute resources available!

Before running Terraform, no Terraform-related entities were available:

No Terraform-related Hypervisor Connection to Google Cloud Platform exists on Citrix Cloud:
cc-nohypconn.png

No Terraform-related Machine Catalog for Google Cloud Platform exists on Citrix Cloud:
cc-nomc.png

No Terraform-related Delivery Group for Google Cloud Platform exists on Citrix Cloud:
cc-nodg.png


The configuration can be started by following the normal Terraform workflow:
terraform init,
terraform plan
and if no errors occur

terraform apply

PS C:\TACG\_CCOnGCP\_CCOnGCP-CCStuff> terraform init

Initializing the backend...

Initializing provider plugins...
- Finding citrix/citrix versions matching ">= 0.5.4"...
- Finding hashicorp/google versions matching ">= 5.21.0"...
- Finding latest version of hashicorp/time...
- Finding latest version of hashicorp/local...
- Installing citrix/citrix v0.5.4...
- Installed citrix/citrix v0.5.4 (signed by a HashiCorp partner, key ID 25D62DD8407EA386)
- Installing hashicorp/google v5.23.0...
- Installed hashicorp/google v5.23.0 (signed by HashiCorp)
- Installing hashicorp/time v0.11.1...
- Installed hashicorp/time v0.11.1 (signed by HashiCorp)
- Installing hashicorp/local v2.5.1...
- Installed hashicorp/local v2.5.1 (signed by HashiCorp)

Partner and community providers are signed by their developers.
If you'd like to know more about provider signing, you can read about it here:
https://www.terraform.io/docs/cli/plugins/signing.html

Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.

PS C:\TACG\_CCOnGCP\_CCOnGCP-CCStuff> terraform plan
data.local_file.LoadZoneID: Reading...
data.local_file.GCPCredentials: Reading...
data.local_file.LoadZoneID: Read complete after 0s [id=fec7404d513834378890c62d7e34d9d692f11957]
data.local_file.GCPCredentials: Read complete after 0s [id=e153c83ee291d1531ec30503aacbb705602bde95]
data.google_compute_network.GCPVPC: Reading...
data.google_compute_network.GCPSubnet: Reading...
data.google_compute_network.GCPSubnet: Read complete after 0s [id=projects/tacg-gcp-XXXXXXXXXX/global/networks/default]
data.google_compute_network.GCPVPC: Read complete after 0s [id=projects/tacg-gcp-XXXXXXXXXX/global/networks/default]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # citrix_delivery_group.CreateDG will be created
  + resource "citrix_delivery_group" "CreateDG" {
      + associated_machine_catalogs = [
          + {
              + machine_catalog = (known after apply)
              + machine_count   = 1
            },
        ]
      + autoscale_settings          = {
          + autoscale_enabled                                   = true
          + disconnect_off_peak_idle_session_after_seconds      = 0
          + disconnect_peak_idle_session_after_seconds          = 300
          + log_off_off_peak_disconnected_session_after_seconds = 0
          + log_off_peak_disconnected_session_after_seconds     = 300
          + off_peak_buffer_size_percent                        = 0
          + off_peak_disconnect_action                          = "Nothing"
          + off_peak_disconnect_timeout_minutes                 = 0
          + off_peak_extended_disconnect_action                 = "Nothing"
          + off_peak_extended_disconnect_timeout_minutes        = 0
          + off_peak_log_off_action                             = "Nothing"
          + peak_buffer_size_percent                            = 0
          + peak_disconnect_action                              = "Nothing"
          + peak_disconnect_timeout_minutes                     = 0
          + peak_extended_disconnect_action                     = "Nothing"
          + peak_extended_disconnect_timeout_minutes            = 0
          + peak_log_off_action                                 = "Nothing"
          + power_off_delay_minutes                             = 30
          + power_time_schemes                                  = [
              + {
                  + days_of_week          = [
                      + "Monday",
                      + "Tuesday",
                      + "Wednesday",
                      + "Thursday",
                      + "Friday",
                    ]
                  + display_name          = "TACG-GCP-TF-AS-Weekdays"
                  + peak_time_ranges      = [
                      + "09:00-17:00",
                    ]
                  + pool_size_schedules   = [
                      + {
                          + pool_size  = 1
                          + time_range = "09:00-17:00"
                        },
                    ]
                  + pool_using_percentage = false
                },
            ]
        }
      + desktops                    = [
          + {
              + description             = "Terraform-based Delivery Group running on GCP"
              + enable_session_roaming  = true
              + enabled                 = true
              + published_name          = "DG-TF-TACG-GCP"
              + restricted_access_users = {
                  + allow_list = [
                      + "TACG-GCP\\vdaallowed",
                    ]
                }
            },
        ]
      + id                          = (known after apply)
      + minimum_functional_level    = "L7_20"
      + name                        = "DG-TF-TACG-GCP"
      + reboot_schedules            = [
          + {
              + days_in_week            = [
                  + "Sunday",
                ]
              + frequency               = "Weekly"
              + frequency_factor        = 1
              + ignore_maintenance_mode = true
              + name                    = "TACG-GCP-Reboot Schedule"
              + natural_reboot_schedule = false
              + reboot_duration_minutes = 0
              + reboot_schedule_enabled = true
              + start_date              = "2024-01-01"
              + start_time              = "02:00"
            },
        ]
      + restricted_access_users     = {
          + allow_list = [
              + "TACG-GCP\\vdaallowed",
            ]
        }
      + total_machines              = (known after apply)
    }

  # citrix_gcp_hypervisor.CreateHypervisorConnection will be created
  + resource "citrix_gcp_hypervisor" "CreateHypervisorConnection" {
      + id                          = (known after apply)
      + name                        = "TACG-GCP-TF-HypConn"
      + service_account_credentials = (sensitive value)
      + service_account_id          = "XXXXXXXXXX@XXXXXXXXXX.iam.gserviceaccount.com"
      + zone                        = "XXXXXXXX-XXXX-XXXX-XXXX-f6a1f864d69a"
    }

  # citrix_gcp_hypervisor_resource_pool.CreateHypervisorPool will be created
  + resource "citrix_gcp_hypervisor_resource_pool" "CreateHypervisorPool" {
      + hypervisor   = (known after apply)
      + id           = (known after apply)
      + name         = "TACG-GCP-TF-HypConnPool"
      + project_name = "TACG-GCP"
      + region       = "europe-west3"
      + subnets      = [
          + "default",
        ]
      + vpc          = "default"
    }

  # citrix_machine_catalog.CreateMCSCatalog will be created
  + resource "citrix_machine_catalog" "CreateMCSCatalog" {
      + allocation_type          = "Random"
      + description              = "Terraform-based Machine Catalog"
      + id                       = (known after apply)
      + is_power_managed         = true
      + is_remote_pc             = false
      + minimum_functional_level = "L7_20"
      + name                     = "MC-TACG-GCP-TF"
      + provisioning_scheme      = {
          + gcp_machine_config             = {
              + master_image = "tacg-gcp-tf-wmi"
              + storage_type = "pd-standard"
            }
          + hypervisor                     = (known after apply)
          + hypervisor_resource_pool       = (known after apply)
          + identity_type                  = "ActiveDirectory"
          + machine_account_creation_rules = {
              + naming_scheme      = "TACG-GCP-W###"
              + naming_scheme_type = "Numeric"
            }
          + machine_domain_identity        = {
              + domain                   = "gcp.the-austrian-citrix-guy.at"
              + domain_ou                = "CN=Computers,DC=gcp,DC=the-austrian-citrix-guy,DC=at"
              + service_account          = "Administrator"
              + service_account_password = (sensitive value)
            }
          + number_of_total_machines       = 1
        }
      + provisioning_type        = "MCS"
      + session_support          = "MultiSession"
      + zone                     = "XXXXXXXX-XXXX-XXXX-XXXX-f6a1f864d69a"
    }

  # time_sleep.wait_60_seconds will be created
  + resource "time_sleep" "wait_60_seconds" {
      + create_duration = "60s"
      + id              = (known after apply)
    }

  # time_sleep.wait_60_seconds_1 will be created
  + resource "time_sleep" "wait_60_seconds_1" {
      + create_duration = "60s"
      + id              = (known after apply)
    }

Plan: 6 to add, 0 to change, 0 to destroy.

Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run "terraform apply"
now.

PS C:\TACG\_CCOnGCP\_CCOnGCP-CCStuff> terraform apply
data.local_file.GCPCredentials: Reading...
data.local_file.LoadZoneID: Reading...
data.local_file.LoadZoneID: Read complete after 0s [id=fec7404d513834378890c62d7e34d9d692f11957]
data.local_file.GCPCredentials: Read complete after 0s [id=e153c83ee291d1531ec30503aacbb705602bde95]
data.google_compute_network.GCPSubnet: Reading...
data.google_compute_network.GCPVPC: Reading...
data.google_compute_network.GCPSubnet: Read complete after 1s [id=projects/tacg-gcp-XXXXXXXXXX/global/networks/default]
data.google_compute_network.GCPVPC: Read complete after 1s [id=projects/tacg-gcp-XXXXXXXXXX/global/networks/default]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # citrix_delivery_group.CreateDG will be created
  + resource "citrix_delivery_group" "CreateDG" {
      + associated_machine_catalogs = [
          + {
              + machine_catalog = (known after apply)
              + machine_count   = 1
            },
        ]
      + autoscale_settings          = {
          + autoscale_enabled                                   = true
          + disconnect_off_peak_idle_session_after_seconds      = 0
          + disconnect_peak_idle_session_after_seconds          = 300
          + log_off_off_peak_disconnected_session_after_seconds = 0
          + log_off_peak_disconnected_session_after_seconds     = 300
          + off_peak_buffer_size_percent                        = 0
          + off_peak_disconnect_action                          = "Nothing"
          + off_peak_disconnect_timeout_minutes                 = 0
          + off_peak_extended_disconnect_action                 = "Nothing"
          + off_peak_extended_disconnect_timeout_minutes        = 0
          + off_peak_log_off_action                             = "Nothing"
          + peak_buffer_size_percent                            = 0
          + peak_disconnect_action                              = "Nothing"
          + peak_disconnect_timeout_minutes                     = 0
          + peak_extended_disconnect_action                     = "Nothing"
          + peak_extended_disconnect_timeout_minutes            = 0
          + peak_log_off_action                                 = "Nothing"
          + power_off_delay_minutes                             = 30
          + power_time_schemes                                  = [
              + {
                  + days_of_week          = [
                      + "Monday",
                      + "Tuesday",
                      + "Wednesday",
                      + "Thursday",
                      + "Friday",
                    ]
                  + display_name          = "TACG-GCP-TF-AS-Weekdays"
                  + peak_time_ranges      = [
                      + "09:00-17:00",
                    ]
                  + pool_size_schedules   = [
                      + {
                          + pool_size  = 1
                          + time_range = "09:00-17:00"
                        },
                    ]
                  + pool_using_percentage = false
                },
            ]
        }
      + desktops                    = [
          + {
              + description             = "Terraform-based Delivery Group running on GCP"
              + enable_session_roaming  = true
              + enabled                 = true
              + published_name          = "DG-TF-TACG-GCP"
              + restricted_access_users = {
                  + allow_list = [
                      + "TACG-GCP\\vdaallowed",
                    ]
                }
            },
        ]
      + id                          = (known after apply)
      + minimum_functional_level    = "L7_20"
      + name                        = "DG-TF-TACG-GCP"
      + reboot_schedules            = [
          + {
              + days_in_week            = [
                  + "Sunday",
                ]
              + frequency               = "Weekly"
              + frequency_factor        = 1
              + ignore_maintenance_mode = true
              + name                    = "TACG-GCP-Reboot Schedule"
              + natural_reboot_schedule = false
              + reboot_duration_minutes = 0
              + reboot_schedule_enabled = true
              + start_date              = "2024-01-01"
              + start_time              = "02:00"
            },
        ]
      + restricted_access_users     = {
          + allow_list = [
              + "TACG-GCP\\vdaallowed",
            ]
        }
      + total_machines              = (known after apply)
    }

  # citrix_gcp_hypervisor.CreateHypervisorConnection will be created
  + resource "citrix_gcp_hypervisor" "CreateHypervisorConnection" {
      + id                          = (known after apply)
      + name                        = "TACG-GCP-TF-HypConn"
      + service_account_credentials = (sensitive value)
      + service_account_id          = "XXXXXXXXXX@XXXXXXXXXX.iam.gserviceaccount.com"
      + zone                        = "XXXXXXXX-XXXX-XXXX-XXXX-f6a1f864d69a"
    }

  # citrix_gcp_hypervisor_resource_pool.CreateHypervisorPool will be created
  + resource "citrix_gcp_hypervisor_resource_pool" "CreateHypervisorPool" {
      + hypervisor   = (known after apply)
      + id           = (known after apply)
      + name         = "TACG-GCP-TF-HypConnPool"
      + project_name = "TACG-GCP"
      + region       = "europe-west3"
      + subnets      = [
          + "default",
        ]
      + vpc          = "default"
    }

  # citrix_machine_catalog.CreateMCSCatalog will be created
  + resource "citrix_machine_catalog" "CreateMCSCatalog" {
      + allocation_type          = "Random"
      + description              = "Terraform-based Machine Catalog"
      + id                       = (known after apply)
      + is_power_managed         = true
      + is_remote_pc             = false
      + minimum_functional_level = "L7_20"
      + name                     = "MC-TACG-GCP-TF"
      + provisioning_scheme      = {
          + gcp_machine_config             = {
              + master_image = "tacg-gcp-tf-wmi"
              + storage_type = "pd-standard"
            }
          + hypervisor                     = (known after apply)
          + hypervisor_resource_pool       = (known after apply)
          + identity_type                  = "ActiveDirectory"
          + machine_account_creation_rules = {
              + naming_scheme      = "TACG-GCP-W###"
              + naming_scheme_type = "Numeric"
            }
          + machine_domain_identity        = {
              + domain                   = "gcp.the-austrian-citrix-guy.at"
              + domain_ou                = "CN=Computers,DC=gcp,DC=the-austrian-citrix-guy,DC=at"
              + service_account          = "XXXXXXXXXX"
              + service_account_password = (sensitive value)
            }
          + number_of_total_machines       = 1
        }
      + provisioning_type        = "MCS"
      + session_support          = "MultiSession"
      + zone                     = "XXXXXXXX-XXXX-XXXX-XXXX-f6a1f864d69a"
    }

  # time_sleep.wait_60_seconds will be created
  + resource "time_sleep" "wait_60_seconds" {
      + create_duration = "60s"
      + id              = (known after apply)
    }

  # time_sleep.wait_60_seconds_1 will be created
  + resource "time_sleep" "wait_60_seconds_1" {
      + create_duration = "60s"
      + id              = (known after apply)
    }

Plan: 6 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

citrix_gcp_hypervisor.CreateHypervisorConnection: Creating...
citrix_gcp_hypervisor.CreateHypervisorConnection: Still creating... [10s elapsed]
citrix_gcp_hypervisor.CreateHypervisorConnection: Still creating... [20s elapsed]
citrix_gcp_hypervisor.CreateHypervisorConnection: Still creating... [30s elapsed]
citrix_gcp_hypervisor.CreateHypervisorConnection: Still creating... [40s elapsed]
citrix_gcp_hypervisor.CreateHypervisorConnection: Creation complete after 41s [id=ceb89ce1-b09f-467d-8fe8-93e59e31bff6]
citrix_gcp_hypervisor_resource_pool.CreateHypervisorPool: Creating...
citrix_gcp_hypervisor_resource_pool.CreateHypervisorPool: Still creating... [10s elapsed]
citrix_gcp_hypervisor_resource_pool.CreateHypervisorPool: Creation complete after 11s [id=506a698f-22bf-4b5f-a910-4618b41890f7]
time_sleep.wait_60_seconds: Creating...
time_sleep.wait_60_seconds: Still creating... [10s elapsed]
time_sleep.wait_60_seconds: Still creating... [20s elapsed]
time_sleep.wait_60_seconds: Still creating... [30s elapsed]
time_sleep.wait_60_seconds: Still creating... [40s elapsed]
time_sleep.wait_60_seconds: Still creating... [51s elapsed]
time_sleep.wait_60_seconds: Creation complete after 1m0s [id=2024-04-04T07:33:16Z]
citrix_machine_catalog.CreateMCSCatalog: Creating...
citrix_machine_catalog.CreateMCSCatalog: Still creating... [10s elapsed]
citrix_machine_catalog.CreateMCSCatalog: Still creating... [20s elapsed]

... ** Output shortened ** ... 

citrix_machine_catalog.CreateMCSCatalog: Still creating... [20m20s elapsed]
citrix_machine_catalog.CreateMCSCatalog: Still creating... [20m30s elapsed]
citrix_machine_catalog.CreateMCSCatalog: Creation complete after 20m35s [id=1410b841-877a-4c7d-bf57-15e310865f36]
time_sleep.wait_60_seconds_1: Creating...
time_sleep.wait_60_seconds_1: Still creating... [10s elapsed]
time_sleep.wait_60_seconds_1: Still creating... [20s elapsed]
time_sleep.wait_60_seconds_1: Still creating... [30s elapsed]
time_sleep.wait_60_seconds_1: Still creating... [40s elapsed]
time_sleep.wait_60_seconds_1: Still creating... [50s elapsed]
time_sleep.wait_60_seconds_1: Creation complete after 1m0s [id=2024-04-04T07:54:51Z]
citrix_delivery_group.CreateDG: Creating...
citrix_delivery_group.CreateDG: Creation complete after 5s [id=9f458d5f-594f-49b5-b2c2-cb920442dc27]

Apply complete! Resources: 6 added, 0 changed, 0 destroyed.
PS C:\TACG\_CCOnGCP\_CCOnGCP-CCStuff>

This configuration completes the full deployment of a Citrix Cloud Resource Location in Google Cloud Platform (GCP).

The environment created by Terraform is now ready for usage, and all entities are in place:

The Resource Location:
cc-rl-ok.png

The Hypervisor Connection and the Hypervisor Pool:
cc-hypconn.png

The Machine Catalog:
cc-mc-ready.png

The Delivery Group and the Worker-VM:cc-dg-ready.png

The Worker-VM:
cc-dg-vm-ready.png

The AutoScale settings of the Delivery Group:
cc-dg-autoscale-ready.png

The Desktop in the Library:
cc-vm-in-library-ready.png

The Desktop in Workspace App:
cc-vm-in-workspace-ready.png

Connection to the Worker-VMs Desktop:
cc-vm-connected.png

 

Appendix

Examples of the Terraform scripts

Module 1: CConGCP-Creation

These are the Terraform configuration files for Module 1 (excerpts):

provider.tf

# Terraform deployment of Citrix DaaS on Google Cloud Platform (GCP)
## Definition of all required Terraform providers
 
terraform {
    required_version = ">= 1.7.5"
 
  required_providers {
    restapi = {
      source  = "Mastercard/restapi"
      version = ">=1.18.2"
    }
 
    citrix = {
      source  = "citrix/citrix"
      version = ">=0.5.4"
    }
 
    google = {
      source = "hashicorp/google"
      version = ">=5.21.0"
    }
  }
}
 
# Configure the Google GCP Provider
provider "google" {
  credentials = file(var.CCOnGCP-Creation-Provider-GCPAuthFileJSON)
  project     = var.CCOnGCP-Creation-Provider-GCPProject
  region      = var.CCOnGCP-Creation-Provider-GCPRegion
  zone        = var.CCOnGCP-Creation-Provider-GCPZone
}
 
# Configure the Citrix Provider
provider "citrix" {
  customer_id   = "${var.CC_CustomerID}"  
  client_id     = "${var.CC_APIKey-ClientID}"  
  client_secret = "${var.CC_APIKey-ClientSecret}"  
}


GetBearerToken.tf

### Create PowerShell file for determining the BearerToken
resource "local_file" "GetBearerToken" {
content  = <<-EOT
 asnp Citrix*
 $key= "${var.CC_APIKey-ClientID}"
 $secret= "${var.CC_APIKey-ClientSecret}"
 $customer= "${var.CC_CustomerID}"
 $XDStoredCredentials = Set-XDCredentials -StoreAs default -ProfileType CloudApi -CustomerId $customer -APIKey $key -SecretKey $secret
 $auth = Get-XDAuthentication
 $BT = $GLOBAL:XDAuthToken | Out-File "${path.module}/GetBT.txt" - Encoding Ascii - NoNewLine
EOT
filename = "${path.module}/GetBT.ps1"
}
 
### Running GetBearertoken-Script to retrieve the Bearer Token
resource "terraform_data" "GetBT" {
  depends_on = [ local_file.GetBearerToken ]
  provisioner "local-exec" {
     command = "${path.module}/GetBT.ps1"
    interpreter = ["PowerShell", "-File"]
 
  }
}
 
### Retrieving the Bearer Token
data "local_file" "Retrieve_BT" {
  depends_on = [ terraform_data.GetBT ]
  filename = "${path.module}/GetBT.txt"
}
 
output "terraform_data_BR_Read" {
  value = data.local_file.Retrieve_BT.content
}


create.tf

# Terraform deployment of Citrix DaaS on Google Cloud Platform
## Creation of all required entities - Networking
 
### Get VPC
data "google_compute_network" "VPC" {
  name                    = "${lower(var.GCP_VPC_Name)}"
}
 
### Get Subnet
data "google_compute_subnetwork" "Subnet" {
  name          = "${lower(var.GCP_VPC_Subnet_Name)}"
 
}
 
### Create Firewall Rule - allow global HTTP
resource "google_compute_firewall" "Allow-HTTP" {
  name    = "${var.GCP_App_Global_Name}-fw-allow-http"
  network = data.google_compute_network.VPC.name
  allow {
    protocol = "tcp"
    ports    = ["80"]
  }
 
  source_ranges = ["0.0.0.0/0"]
  target_tags = ["http"]
}
 
### Create Firewall Rule - allow global HTTPS
resource "google_compute_firewall" "Allow-HTTPS" {
  name    = "${var.GCP_App_Global_Name}-fw-allow-https"
  network = data.google_compute_network.VPC.name
  allow {
    protocol = "tcp"
    ports    = ["443"]
  }
 
  source_ranges = ["0.0.0.0/0"]
  target_tags = ["https"]
}
 
### Create Firewall Rule - allow Subnet SSH
resource "google_compute_firewall" "Allow-SSH" {
  name    = "${var.GCP_App_Global_Name}-fw-allow-ssh"
  network = data.google_compute_network.VPC.name
  allow {
    protocol = "tcp"
    ports    = ["22"]
  }
 
  source_ranges = ["${data.google_compute_subnetwork.Subnet.ip_cidr_range}"]
  target_tags = ["ssh"]
}
 
### Create Firewall Rule - allow global RDP
resource "google_compute_firewall" "Allow-RDP" {
  name    = "${var.GCP_App_Global_Name}-fw-allow-rdp"
  network = data.google_compute_network.VPC.name
  allow {
    protocol = "tcp"
    ports    = ["3389"]
  }
 
  source_ranges = ["0.0.0.0/0"]
  target_tags = ["rdp"]
}
 
### Create Firewall Rule - allow Subnet WinRM
resource "google_compute_firewall" "Allow-WinRM" {
  name    = "${var.GCP_App_Global_Name}-fw-allow-winrm"
  network = data.google_compute_network.VPC.name
  allow {
    protocol = "tcp"
    ports    = ["5985","5986"]
  }
 
  source_ranges = ["${data.google_compute_subnetwork.Subnet.ip_cidr_range}"]
  target_tags = ["winrm"]
}
 
### Create Firewall Rule - allow Subnet All TCP
resource "google_compute_firewall" "Allow-All-TCP" {
  name    = "${var.GCP_App_Global_Name}-fw-allow-all-tcp"
  network = data.google_compute_network.VPC.name
  allow {
    protocol = "tcp"
    ports    = ["0-65534"]
  }
 
  source_ranges = ["${data.google_compute_subnetwork.Subnet.ip_cidr_range}"]
  target_tags = ["all-tcp"]
}
 
### Create Firewall Rule - allow Subnet All UDP
resource "google_compute_firewall" "Allow-All-UDP" {
  name    = "${var.GCP_App_Global_Name}-fw-allow-all-udp"
  network = data.google_compute_network.VPC.name
  allow {
    protocol = "udp"
    ports    = ["0-65534"]
  }
 
  source_ranges = ["${data.google_compute_subnetwork.Subnet.ip_cidr_range}"]
  target_tags = ["all-udp"]
}
 
### Create Firewall Rule - allow Subnet All ICMP
resource "google_compute_firewall" "Allow-All-ICMP" {
  name    = "${var.GCP_App_Global_Name}-fw-allow-all-icmp"
  network = data.google_compute_network.VPC.name
  allow {
    protocol = "icmp"
  }
 
  source_ranges = ["${data.google_compute_subnetwork.Subnet.ip_cidr_range}"]
  target_tags = ["all-icmp"]
}
 
### Create static internal IP address for CC1
resource "google_compute_address" "internal-ip-cc1" {
  name         ="internal-ip-cc1"
  subnetwork   = data.google_compute_subnetwork.Subnet.id
  address_type = "INTERNAL"
  address      = "${var.GCP_VPC_InternalIP_CC1}"
  region       = "${var.CCOnGCP-Creation-Provider-GCPRegion}"
}
 
### Create static internal IP address for CC2
resource "google_compute_address" "internal-ip-cc2" {
  name         ="internal-ip-cc2"
  subnetwork   = data.google_compute_subnetwork.Subnet.id
  address_type = "INTERNAL"
  address      = "${var.GCP_VPC_InternalIP_CC2}"
  region       = "${var.CCOnGCP-Creation-Provider-GCPRegion}"
}
 
### Create static internal IP Address for Admin-VM
resource "google_compute_address" "internal-ip-adminvm" {
  name         ="internal-ip-avm"
  subnetwork   = data.google_compute_subnetwork.Subnet.id
  address_type = "INTERNAL"
  address      = "${var.GCP_VPC_InternalIP_AdminVM}"
  region       = "${var.CCOnGCP-Creation-Provider-GCPRegion}"
}
 
### Create static internal IP Address for WMI
resource "google_compute_address" "internal-ip-wmi" {
  name         ="internal-ip-wmi"
  subnetwork   = data.google_compute_subnetwork.Subnet.id
  address_type = "INTERNAL"
  address      = "${var.GCP_VPC_InternalIP_WMI}"
  region       = "${var.CCOnGCP-Creation-Provider-GCPRegion}"
}
 
### Create PowerShell file for joining the domain
resource "local_file" "CreateDomainJoinScript" {
content  = <<-EOT
Start-Sleep -Seconds 10
net user administrator /active:yes
net user administrator ${var.GCP_App_Global_LocalAdminPW}
netdom.exe join $env:COMPUTERNAME /domain:'${var.GCP_App_Global_FullDomainName}' /UserD:'${var.GCP_App_Global_DomainAdminUPN}' /PasswordD:'${var.GCP_App_Global_DomainAdminPW}' /reboot:5
EOT
filename = "${path.module}/TF-Domain-Join-Script.ps1"
}
 
### Create a Storage Bucket for storage of all needed stuff
resource "google_storage_bucket" "Prereqs" {
 depends_on = [ local_file.CreateDomainJoinScript ]
 name          = "${var.GCP_App_Global_Name}-storagebucket"
 location      = "EU"
 storage_class = "STANDARD"
 force_destroy = true
}
 
resource "google_storage_bucket_access_control" "Prereqs" {
  bucket = google_storage_bucket.Prereqs.name
  role   = "READER"
  entity = "allUsers"
}
 
resource "google_storage_bucket_iam_binding" "iam" {
  depends_on = [ google_storage_bucket.Prereqs ]
  bucket = google_storage_bucket.Prereqs.name
  members = [ "allUsers" ]
  role = "roles/storage.objectViewer"
}
 
#### Upload all required Software
resource "google_storage_bucket_object" "Prereqs" {
  depends_on = [ google_storage_bucket.Prereqs ]
  name   = "domain-join-script.ps1"
  source = "${path.module}/TF-Domain-Join-Script.ps1"
  bucket = google_storage_bucket.Prereqs.name
}
 
resource "google_storage_bucket_object" "CC" {
  depends_on = [ google_storage_bucket.Prereqs ]
  name   = "cwcconnector.exe"
  source = "${path.module}/DATA/cwcconnector.exe"
  bucket = google_storage_bucket.Prereqs.name
}
 
resource "google_storage_bucket_object" "PoSH" {
  depends_on = [ google_storage_bucket.Prereqs ]
  name   = "CitrixPoSHSDK.exe"
  source = "${path.module}/DATA/CitrixPoSHSDK.exe"
  bucket = google_storage_bucket.Prereqs.name
}
 
resource "google_storage_bucket_object" "CHC" {
  depends_on = [ google_storage_bucket.Prereqs ]
  name   = "CloudHealthCheckInstaller_x64.msi"
  source = "${path.module}/DATA/CloudHealthCheckInstaller_x64.msi"
  bucket = google_storage_bucket.Prereqs.name
}
 
## Create the VMs
### Create the CC1-VM
resource "google_compute_instance" "CC1" {
  depends_on = [ google_compute_address.internal-ip-cc1,google_storage_bucket_object.Prereqs ]
  name         = "${var.GCP_VM_CC1_Name}"
  hostname     = "${var.GCP_VM_CC1_Name}.${var.GCP_App_Global_FullDomainName}"
  machine_type = "${var.GCP_VM_CC_InstanceType}"
  zone         = "${var.CCOnGCP-Creation-Provider-GCPZone}"
  tags         = ["rdp","http","https","winrm", "all-tcp", "all-udp", "all-icmp","http-server","https-server"]
 
  boot_disk {
    initialize_params {
      image = "${var.GCP_VM_CC_ImageName}"
    }
  }
 
  metadata = {
    sysprep-specialize-script-url = "https://storage.googleapis.com/tacg-gcp-tf-storagebucket/domain-join-script.ps1"
 
  }
 
  network_interface {
    network       = data.google_compute_network.VPC.name
    subnetwork    = data.google_compute_subnetwork.Subnet.name
    network_ip    = "${var.GCP_VPC_InternalIP_CC1}"
    access_config { }
  }
 
  service_account {
    email         = "${var.GCP_VM_ServiceAccount}"
    scopes        = ["cloud-platform"]
  }
}
 
### Create the CC2-VM
resource "google_compute_instance" "CC2" {
  depends_on = [ google_compute_address.internal-ip-cc2,google_storage_bucket_object.Prereqs ]
  name         = "${var.GCP_VM_CC2_Name}"
  hostname     = "${var.GCP_VM_CC2_Name}.${var.GCP_App_Global_FullDomainName}"
  machine_type = "${var.GCP_VM_CC_InstanceType}"
  zone         = "${var.CCOnGCP-Creation-Provider-GCPZone}"
  tags         = ["rdp","http","https","winrm", "all-tcp", "all-udp", "all-icmp","http-server","https-server"]
 
  boot_disk {
    initialize_params {
      image = "${var.GCP_VM_CC_ImageName}"
    }
  }
 
  metadata = {
    sysprep-specialize-script-url = "https://storage.googleapis.com/tacg-gcp-tf-storagebucket/domain-join-script.ps1"
 
  }
 
  network_interface {
    network       = data.google_compute_network.VPC.name
    subnetwork    = data.google_compute_subnetwork.Subnet.name
    network_ip    = "${var.GCP_VPC_InternalIP_CC2}"
    access_config { }
  }
 
   service_account {
    email         = "${var.GCP_VM_ServiceAccount}"
    scopes        = ["cloud-platform"]
  }
}
 
### Create the Admin-VM
resource "google_compute_instance" "AdminVM" {
  depends_on = [ google_compute_address.internal-ip-adminvm, google_storage_bucket_object.Prereqs ]
  name         = "${var.GCP_VM_AdminVM_Name}"
  hostname     = "${var.GCP_VM_AdminVM_Name}.${var.GCP_App_Global_FullDomainName}"
  machine_type = "${var.GCP_VM_CC_InstanceType}"
  zone         = "${var.CCOnGCP-Creation-Provider-GCPZone}"
  tags         = ["rdp","http","https","winrm", "all-tcp", "all-udp", "all-icmp","http-server","https-server"]
 
  boot_disk {
    initialize_params {
      image = "${var.GCP_VM_CC_ImageName}"
    }
  }
 
  metadata = {
    sysprep-specialize-script-url = "https://storage.googleapis.com/tacg-gcp-tf-storagebucket/domain-join-script.ps1"
    #sysprep-specialize-script-url = google_storage_bucket_object.Prereqs.name
  }
 
  network_interface {
    network       = data.google_compute_network.VPC.name
    subnetwork    = data.google_compute_subnetwork.Subnet.name
    network_ip    = "${var.GCP_VPC_InternalIP_AdminVM}"
    access_config { }
  }
 
   service_account {
    email         = "${var.GCP_VM_ServiceAccount}"
    scopes        = ["cloud-platform"]
  }
}
 
 ### Create the WMI-VM
resource "google_compute_instance" "WMI" {
  depends_on = [ google_compute_address.internal-ip-wmi, google_storage_bucket_object.Prereqs ]
  name         = "${var.GCP_VM_WMI_Name}"
  hostname     = "${var.GCP_VM_WMI_Name}.${var.GCP_App_Global_FullDomainName}"
  machine_type = "${var.GCP_VM_CC_InstanceType_WMI}"
  zone         = "${var.CCOnGCP-Creation-Provider-GCPZone}"
  tags         = ["rdp","http","https","winrm", "all-tcp", "all-udp", "all-icmp","http-server","https-server"]
 
  boot_disk {
    initialize_params {
      image = "${var.GCP_VM_CC_ImageName}"
    }
  }
 
  metadata = {
    sysprep-specialize-script-url = "https://storage.googleapis.com/tacg-gcp-tf-storagebucket/domain-join-script.ps1"
 
  }
 
  network_interface {
    network       = data.google_compute_network.VPC.name
    subnetwork    = data.google_compute_subnetwork.Subnet.name
    network_ip    = "${var.GCP_VPC_InternalIP_WMI}"
    access_config { }
  }
 
   service_account {
    email         = "${var.GCP_VM_ServiceAccount}"
    scopes        = ["cloud-platform"]
  }
}

 

Module 2: CConGCP-Install

These are the Terraform configuration files for Module 2 (excerpts):

provider.tf

# Terraform deployment of Citrix DaaS on Google Cloud Platform (GCP)
## Definition of all required Terraform providers
 
terraform {
    required_version = ">= 1.7.5"
 
  required_providers {
    restapi = {
      source  = "Mastercard/restapi"
      version = ">=1.18.2"
    }
 
    citrix = {
      source  = "citrix/citrix"
      version = ">=0.5.4"
    }
 
    google = {
      source = "hashicorp/google"
      version = ">=5.21.0"
    }
  }
}
 
# Configure the Google GCP Provider
provider "google" {
  credentials = file(var.CCOnGCP-Creation-Provider-GCPAuthFileJSON)
  project     = var.CCOnGCP-Creation-Provider-GCPProject
  region      = var.CCOnGCP-Creation-Provider-GCPRegion
  zone        = var.CCOnGCP-Creation-Provider-GCPZone
}
 
# Configure the Citrix Provider
provider "citrix" {
  customer_id   = "${var.CC_CustomerID}"  
  client_id     = "${var.CC_APIKey-ClientID}"  
  client_secret = "${var.CC_APIKey-ClientSecret}"  
}


CreatePreReqs.tf

# Terraform deployment of Citrix DaaS on Google Cloud Platform
locals {
 
}
## Create all Pre-requisites
 
### Create local directory
resource "local_file" "Log" {
  content  = "Directory created."
  filename = "${var.CC_Install_LogPath}/log.txt"
}
 
resource "local_file" "LogData" {
  depends_on = [ local_file.Log ]  
  content  = "Directory created."
  filename = "${var.CC_Install_LogPath}/DATA/log.txt"
}
 
data "google_compute_address" "IPOfCC1" {
  name = "internal-ip-cc1"
}
 
data "google_compute_address" "IPOfCC2" {
  name = "internal-ip-cc2"
}
 
### Create PowerShell file for installing the Citrix Remote PoSH SDK on AVM
resource "local_file" "InstallPoSHSDKOnAVM" {
content  = <<-EOT
#### Need to invoke PowerShell as Domain User as the provisioner does not allow to be run in a Domain Users-context
$PSUsername = '${var.Provisioner_DomainAdmin-Username}'
$PSPassword = '${var.Provisioner_DomainAdmin-Password}'
$PSSecurePassword = ConvertTo-SecureString $PSPassword -AsPlainText -Force
$PSCredential = New-Object System.Management.Automation.PSCredential ($PSUsername, $PSSecurePassword)
Invoke-Command -ComputerName localhost -Credential $PSCredential -ScriptBlock {
$path = "${var.CC_Install_LogPath}"
If(!(test-path -PathType container $path))
{
    New-Item -ItemType Directory -Path $path
}
Add-Content ${var.CC_Install_LogPath}/log.txt "`nScript started."
# Download Citrix Remote PowerShell SDK
Invoke-WebRequest '${var.CC_Install_RPoSHURI}' -OutFile '${var.CC_Install_LogPath}/DATA/CitrixPoshSdk.exe'
Add-Content ${var.CC_Install_LogPath}/log.txt "`nPowerShell SDK downloaded."
# Install Citrix Remote PowerShell SDK
Start-Process -Filepath "${var.CC_Install_LogPath}/DATA/CitrixPoshSdk.exe" -ArgumentList "-quiet"
Add-Content ${var.CC_Install_LogPath}/log.txt "`nPowerShell SDK installed."
# Timeout to settle all processes
Start-Sleep -Seconds 60
Add-Content ${var.CC_Install_LogPath}/log.txt "`nTimeout elapsed."
}
EOT
filename = "${var.CC_Install_LogPath}/DATA/InstallPoSHSDKOnAVM.ps1"
}
 
#### Execute Pre-Reqs-Script on AVM
resource "null_resource" "ExecuteInstallPoSHSDKOnAVM" {
  provisioner "local-exec" {
    command = " ${var.CC_Install_LogPath}/DATA/InstallPoSHSDKOnAVM.ps1"
    interpreter = ["PowerShell", "-Command"]
  }
}
 
### Create PowerShell file for extracting the GCP credentials in a Tarraform variable file on AVM
resource "local_file" "ExtractGCPCredentialsToVarFile" {
content  = <<-EOT
#### Need to invoke PowerShell as Domain User as the provisioner does not allow to be run in a Domain Users-context
$PSUsername = '${var.Provisioner_DomainAdmin-Username}'
$PSPassword = '${var.Provisioner_DomainAdmin-Password}'
$PSSecurePassword = ConvertTo-SecureString $PSPassword -AsPlainText -Force
$PSCredential = New-Object System.Management.Automation.PSCredential ($PSUsername, $PSSecurePassword)
Invoke-Command -ComputerName localhost -Credential $PSCredential -ScriptBlock {
Add-Content ${var.CC_Install_LogPath}/log.txt "`nGCP-Extraction Script started."
$json = Get-Content '${var.CC_Install_LogPath}/${var.CCOnGCP-Creation-Provider-GCPAuthFileJSON}' | Out-String | ConvertFrom-Json
$key =  "`"tv_private_key`"" + ":" + "`"" + $json.private_key + "`""
$keycorr = [string]::join("\n",($key.Split("`n")))
$email = "`"tv_client_email`"" + ":" + "`"" + $json.client_email + "`""
$Path = 'c:/TACG/_CCOnGCP/_CCOnGCP-CCStuff/_CCOnGCP-CCStuff-CreateCCEntities-GCP.auto.tfvars.json'
Set-Content -Path $Path -Encoding Ascii -Value "{"
Add-Content -Path $Path -Encoding Ascii -Value ($keycorr + ",")
Add-Content -Path $Path -Encoding Ascii -Value $email
Add-Content -Path $Path -NoNewline -Encoding Ascii -Value '}'
Add-Content ${var.CC_Install_LogPath}/log.txt "`nGCP-Extraction Script stopped."
}
EOT
filename = "${var.CC_Install_LogPath}/DATA/ExtractGCPCredentialsToVarFile.ps1"
}
 
#### Execute Pre-Reqs-Script on AVM
resource "null_resource" "ExecuteExtractGCPCredentialsToVarFile" {
  provisioner "local-exec" {
    command = " ${var.CC_Install_LogPath}/DATA/ExtractGCPCredentialsToVarFile.ps1"
    interpreter = ["PowerShell", "-Command"]
  }
}
 
### Create CWC-Installer configuration file based on variables and save it into Transfer directory
resource "local_file" "CWC-Configuration" {
  content  = jsonencode(
        {
        "customerName" = "${var.CC_CustomerID}",
        "clientId" = "${var.CC_APIKey-ClientID}",
        "clientSecret" = "${var.CC_APIKey-ClientSecret}",
        "resourceLocationId" = "XXXXXXXXXX",
        "acceptTermsOfService" = true
        }
      )
  filename = "${var.CC_Install_LogPath}/DATA/cwc.json"
 
}
 
 
### Retrieving the BearerToken
#### Create PowerShell script to download BearerToken
resource "local_file" "GetBearerToken" {
content  = <<-EOT
 asnp Citrix*
 $key= "${var.CC_APIKey-ClientID}"
 $secret= "${var.CC_APIKey-ClientSecret}"
 $customer= "${var.CC_CustomerID}"
 $XDStoredCredentials = Set-XDCredentials -StoreAs default -ProfileType CloudApi -CustomerId $customer -APIKey $key -SecretKey $secret
 $auth = Get-XDAuthentication
 $BT = $GLOBAL:XDAuthToken | Out-File "${var.CC_Install_LogPath}/DATA/GetBT.txt" -NoNewline -Encoding Ascii
EOT
filename = "${var.CC_Install_LogPath}/DATA/GetBT.ps1"
}
 
#### Running GetBearertoken-Script to retrieve the Bearer Token
resource "terraform_data" "GetBT" {
  depends_on = [ local_file.GetBearerToken, null_resource.ExecuteInstallPoSHSDKOnAVM]
 
  provisioner "local-exec" {
     command = "${var.CC_Install_LogPath}/DATA/GetBT.ps1"
    interpreter = ["PowerShell", "-File"]
 
  }
}
 
### Create a dedicated Resource Location in Citrix Cloud
#### Create the script to create a dedicated Resource Location in Citrix Cloud
resource "local_file" "CreateRLScript" {
  depends_on = [ terraform_data.GetBT ]
  content  = <<-EOT
 Add-Content ${var.CC_Install_LogPath}/log.txt "`nCreateRL-Script started."
$CCCustomerID = "${var.CC_CustomerID}"
$CCBearerToken = Get-Content -Path ${var.CC_Install_LogPath}/DATA/GetBT.txt -Force
$CCName ="${var.CC_RestRLName}"
$CCGuid = New-Guid
#
$requestUri = "https://api-eu.cloud.com/resourcelocations"
$headers = @{ "Accept"="application/json"; "Authorization" = $CCBearerToken; "Citrix-CustomerId" = $CCCustomerID; "Content-Type" = "application/json" }
$Body = @{ "id"=$CCGuid; "name" = $CCName; "internalOnly" = $false; "timeZone" = "GMT Standard Time"; "readOnly" = $false}
$Bodyjson = $Body | Convertto-Json -Depth 3
$response = Invoke-RestMethod -Uri $requestUri -Method POST -Headers $headers -Body $Bodyjson -ContentType "application/json"
Add-Content ${var.CC_Install_LogPath}/log.txt "`n$response"
Add-Content ${var.CC_Install_LogPath}/log.txt "`nCreateRL-Script finished."
  EOT
  filename = "${var.CC_Install_LogPath}/DATA/CreateRL.ps1"
}
 
#### Running the Resource Location-Script to generate the Resource Location
resource "terraform_data" "ResourceLocation" {
  depends_on = [ local_file.CreateRLScript ]
  provisioner "local-exec" {
     command = "${var.CC_Install_LogPath}/DATA/CreateRL.ps1"
    interpreter = ["PowerShell", "-File"]
 
  }
}
 
#### Wait 10 mins after RL creation to settle Zone creation
resource "time_sleep" "wait_900_seconds" {
  depends_on = [ terraform_data.ResourceLocation ]
  create_duration = "900s"
}
 
#### Wait 1 mins after RL creation to settle Zone creation
resource "time_sleep" "wait_60_seconds" {
  depends_on = [ terraform_data.ResourceLocation ]
  create_duration = "60s"
}
 
### Create PowerShell file for determining the SiteID
resource "local_file" "GetSiteIDScript" {
  depends_on = [time_sleep.wait_60_seconds]
  content  = <<-EOT
Add-Content ${var.CC_Install_LogPath}/log.txt "`nSiteID-Script started."
$requestUri = "https://api-eu.cloud.com/cvad/manage/me"
$CCBearerToken = Get-Content -Path ${var.CC_Install_LogPath}/DATA/GetBT.txt -Force
$headers = @{ "Accept"="application/json"; "Authorization" = $CCBearerToken; "Citrix-CustomerId" = "${var.CC_CustomerID}" }
$response = Invoke-RestMethod -Uri $requestUri -Method GET -Headers $headers | Select-Object Customers
$responsetojson = $response | Convertto-Json -Depth 3
$responsekorr = $responsetojson -replace("null","""empty""")
$responsefromjson = $responsekorr | Convertfrom-json
$SitesObj=$responsefromjson.Customers[0].Sites[0]
$Export1 = $SitesObj -replace("@{Id=","")
$SplittedString = $Export1.Split(";")
$SiteID= $SplittedString[0]
$PathCompl = "${var.CC_Install_LogPath}/DATA/GetSiteID.txt"
Set-Content -Path $PathCompl -Value $SiteID -NoNewline -Encoding Ascii
Add-Content ${var.CC_Install_LogPath}/log.txt "`nSiteID-Script successfully completed."
  EOT
  filename = "${var.CC_Install_LogPath}/DATA/GetSiteID.ps1"
}
 
#### Running the SiteID-Script to generate the SiteID
resource "terraform_data" "SiteID" {
  depends_on = [ local_file.GetSiteIDScript ]
  provisioner "local-exec" {
     command = "${var.CC_Install_LogPath}/DATA/GetSiteID.ps1"
    interpreter = ["PowerShell", "-File"]
 
  }
}
 
### Create PowerShell file for determining the ZoneID
resource "local_file" "GetZoneIDScript" {
  depends_on = [time_sleep.wait_900_seconds,terraform_data.SiteID]
  content  = <<-EOT
Add-Content ${var.CC_Install_LogPath}/log.txt "`nZoneID-Script started."
$requestUri = "https://api-eu.cloud.com/cvad/manage/Zones"
$CCBearerToken = Get-Content -Path ${var.CC_Install_LogPath}/DATA/GetBT.txt -Force
$CCSiteID = Get-Content -Path ${var.CC_Install_LogPath}/DATA/GetSiteID.txt -Force
Add-Content ${var.CC_Install_LogPath}/log.txt "`nBearer-Token: $CCBearerToken"
Add-Content ${var.CC_Install_LogPath}/log.txt "`nSiteID: $CCSiteID"
$headers = @{ "Accept"="application/json"; "Authorization" = $CCBearerToken; "Citrix-CustomerId" = "${var.CC_CustomerID}"; "Citrix-InstanceId" = $CCSiteID }
Add-Content ${var.CC_Install_LogPath}/log.txt "`nHeader: $headers"
$response = Invoke-RestMethod -Uri $requestUri -Method GET -Headers $headers | Convertto-Json
Add-Content ${var.CC_Install_LogPath}/log.txt "`nResponse: $response"
$responsedejson = $response | ConvertFrom-Json
Add-Content ${var.CC_Install_LogPath}/log.txt "`nResponseDeJSON: $responsedejeson"
$ZoneId = $responsedejson.Items | Where-Object { $_.Name -eq "${var.CC_RestRLName}" } | Select-Object id
$Export1 = $ZoneId -replace("@{Id=","")
$ZoneID = $Export1 -replace("}","")
$PathCompl = "${var.CC_Install_LogPath}/DATA/GetZoneID.txt"
Set-Content -Path $PathCompl -Value $ZoneID -NoNewline -Encoding Ascii
Add-Content ${var.CC_Install_LogPath}/log.txt "`nZoneID-Script completed."
  EOT
  filename = "${var.CC_Install_LogPath}/DATA/GetZoneID.ps1"
}
 
#### Running the ZoneID-Script to generate the ZoneID
resource "terraform_data" "ZoneID" {
  depends_on = [ local_file.GetZoneIDScript ]
  provisioner "local-exec" {
     command = "${var.CC_Install_LogPath}/DATA/GetZoneID.ps1"
    interpreter = ["PowerShell", "-File"]
 
  }
}
 
resource "terraform_data" "ZoneID2" {
  depends_on = [ terraform_data.ZoneID ]
  provisioner "local-exec" {
     command = "${var.CC_Install_LogPath}/DATA/GetZoneID.ps1"
    interpreter = ["PowerShell", "-File"]
 
  }
}
 
#### Change RL-ID in CWC-JSON file to valid Zone-ID
resource "local_file" "CreateValidCWCOnAVM" {
  depends_on = [ terraform_data.ZoneID2 ]
content  = <<-EOT
Add-Content ${var.CC_Install_LogPath}/log.txt "`nCWC-Script started."
#### Need to invoke PowerShell as Domain User as the provisioner does not allow to be run in a Domain Users-context
$PSUsername = '${var.Provisioner_DomainAdmin-Username}'
$PSPassword = '${var.Provisioner_DomainAdmin-Password}'
$PSSecurePassword = ConvertTo-SecureString $PSPassword -AsPlainText -Force
$PSCredential = New-Object System.Management.Automation.PSCredential ($PSUsername, $PSSecurePassword)
Invoke-Command -ComputerName localhost -Credential $PSCredential -ScriptBlock {
$path = "${var.CC_Install_LogPath}"
# Correct the Resource Location ID in cwc.json file
$requestUri = "https://api-eu.cloud.com/resourcelocations"
$CCBearerToken = Get-Content -Path ${var.CC_Install_LogPath}/DATA/GetBT.txt -Force
$CCSiteID = Get-Content -Path ${var.CC_Install_LogPath}/DATA/GetSiteID.txt -Force
$CCZoneID = Get-Content -Path ${var.CC_Install_LogPath}/DATA/GetZoneID.txt -Force
$headers = @{ "Accept"="application/json"; "Authorization" =  $CCBearerToken; "Citrix-CustomerId" = "${var.CC_CustomerID}"}
$response = Invoke-RestMethod -Uri $requestUri -Method GET -Headers $headers | Convertto-Json
Add-Content ${var.CC_Install_LogPath}/log.txt "`nCWC-Response: $response"
$RLs = ConvertFrom-Json $response
$RLFiltered = $RLs.items | Where-Object name -in "${var.CC_RestRLName}"
Add-Content ${var.CC_Install_LogPath}/log.txt $RLFiltered
$RLID = $RLFiltered.id
$OrigContent = Get-Content ${var.CC_Install_LogPath}/DATA/cwc.json
Add-Content ${var.CC_Install_LogPath}/log.txt $RLID
Add-Content ${var.CC_Install_LogPath}/log.txt $OrigContent
$CorrContent = $OrigCOntent.Replace('XXXXXXXXXX', $RLID) | Out-File -FilePath ${var.CC_Install_LogPath}/DATA/cwc.json -NoNewline -Encoding Ascii
$PathCompl = "${var.CC_Install_LogPath}/DATA/GetRLID.txt"
Set-Content -Path $PathCompl -Value $RLID -NoNewline -Encoding Ascii
Add-Content ${var.CC_Install_LogPath}/log.txt "`ncwc.json corrected."
Add-Content ${var.CC_Install_LogPath}/log.txt "`nCWC-Script completed."
}
EOT
filename = "${var.CC_Install_LogPath}/DATA/CreateValidCWCOnAVM.ps1"
}
 
#### Running the CWC-Script to generate the ZoneID
resource "terraform_data" "ExecuteCreateValidCWCOnAVM" {
  depends_on = [ local_file.CreateValidCWCOnAVM ]
  provisioner "local-exec" {
     command = "${var.CC_Install_LogPath}/DATA/CreateValidCWCOnAVM.ps1"
    interpreter = ["PowerShell", "-File"]
 
  }
}
 
#### Create PowerShell file for CWC-Installer-Script for CCs
##### Check %LOCALAPPDATA%\Temp\CitrixLogs\CloudServicesSetup and %ProgramData%\Citrix\WorkspaceCloud\InstallLogs for logs!!!!!
resource "local_file" "InstallCWCOnCC" {
  #depends_on = [ terraform_data.ExecuteCreateValidCWCOnAVM ]
  content  = <<-EOT
  #### Need to invoke PowerShell as Domain User as the provisioner does not allow to be run in a Domain Users-context
  $PSUsername = '${var.Provisioner_DomainAdmin-Username}'
  $PSPassword = '${var.Provisioner_DomainAdmin-Password}'
  $PSSecurePassword = ConvertTo-SecureString $PSPassword -AsPlainText -Force
  $PSCredential = New-Object System.Management.Automation.PSCredential ($PSUsername, $PSSecurePassword)
  Invoke-Command -ComputerName localhost -Credential $PSCredential -ScriptBlock {
  $path = "${var.CC_Install_LogPath}"
  If(!(test-path -PathType container $path))
 {
      New-Item -ItemType Directory -Path $path
 }
 
  Add-Content ${var.CC_Install_LogPath}/log.txt "`nScript started."
  # Download the Citrix Cloud Connector-Software to CC
  Invoke-WebRequest ${var.CC_Install_CWCURI} -OutFile '${var.CC_Install_LogPath}/DATA/CWCConnector.exe'
  # Install Citrix Cloud Controller based on the cwc.json configuration file
  # Check %LOCALAPPDATA%\Temp\CitrixLogs\CloudServicesSetup and %ProgramData%\Citrix\WorkspaceCloud\InstallLogs for logs!!!!!
  Add-Content ${var.CC_Install_LogPath}/log.txt "`nInstalling Cloud Connector."
  Start-Process -Wait -Filepath "${var.CC_Install_LogPath}/DATA/CWCConnector.exe" -ArgumentList "/q /ParametersFilePath:${var.CC_Install_LogPath}/DATA/cwc.json"
  Add-Content ${var.CC_Install_LogPath}/log.txt "`nInstalled Cloud Connector."
  #Restart-Computer -Force
  }
  EOT
  filename = "${var.CC_Install_LogPath}/DATA/InstallCWCOnCC.ps1"
}
 
#### Create restart script
resource "local_file" "RestartCC" {
  #depends_on = [ terraform_data.ExecuteCreateValidCWCOnAVM ]
  content  = <<-EOT
  #### Need to invoke PowerShell as Domain User as the provisioner does not allow to be run in a Domain Users-context
  $PSUsername = '${var.Provisioner_DomainAdmin-Username}'
  $PSPassword = '${var.Provisioner_DomainAdmin-Password}'
  $PSSecurePassword = ConvertTo-SecureString $PSPassword -AsPlainText -Force
  $PSCredential = New-Object System.Management.Automation.PSCredential ($PSUsername, $PSSecurePassword)
  Invoke-Command -ComputerName localhost -Credential $PSCredential -ScriptBlock {
  Restart-Computer -Force
  }
  EOT
  filename = "${var.CC_Install_LogPath}/DATA/RestartCC.ps1"
}
 
#######################################################################################################################################################################
 
### Upload required components to CC1
#### Set the Provisioner-Connection
resource "null_resource" "UploadRequiredComponentsToCC1" {
 depends_on = [ local_file.InstallCWCOnCC ]
 connection {
    type            = var.Provisioner_Type
    user            = var.Provisioner_Admin-Username
    password        = var.Provisioner_Admin-Password
    host            = data.google_compute_address.IPOfCC1.address
    timeout         = var.Provisioner_Timeout
 
  }
 
###### Upload Cloud Connector configuration file to CC1
  provisioner "file" {
    source      = "${var.CC_Install_LogPath}/DATA/cwc.json"
    destination = "${var.CC_Install_LogPath}/DATA/cwc.json"
   
  }
 
###### Upload SiteID file to CC1
  provisioner "file" {
    source      = "${var.CC_Install_LogPath}/DATA/GetSiteID.txt"
    destination = "${var.CC_Install_LogPath}/DATA/GetSiteID.txt"
   
  }
 
###### Upload ZoneID file to CC1
  provisioner "file" {
    source      = "${var.CC_Install_LogPath}/DATA/GetZoneID.txt"
    destination = "${var.CC_Install_LogPath}/DATA/GetZoneID.txt"
   
  }
 
###### Upload RLID file to CC1
  provisioner "file" {
    source      = "${var.CC_Install_LogPath}/DATA/GetRLID.txt"
    destination = "${var.CC_Install_LogPath}/DATA/GetRLID.txt"
   
  }
 
###### Upload PreReqs script to CC1
  provisioner "file" {
    source      = "${var.CC_Install_LogPath}/DATA/InstallCWCOnCC.ps1"
    destination = "${var.CC_Install_LogPath}/DATA/InstallCWCOnCC.ps1"
   
  }
 
###### Upload Restart script to CC1
  provisioner "file" {
    source      = "${var.CC_Install_LogPath}/DATA/RestartCC.ps1"
    destination = "${var.CC_Install_LogPath}/DATA/RestartCC.ps1"
   
  }
}
 
###### Execute the PreReqs script on CC1
resource "null_resource" "CallRequiredScriptsOnCC1" {
 depends_on = [ null_resource.UploadRequiredComponentsToCC1 ]
 connection {
    type            = var.Provisioner_Type
    user            = var.Provisioner_Admin-Username
    password        = var.Provisioner_Admin-Password
    host            = data.google_compute_address.IPOfCC1.address
    timeout         = var.Provisioner_Timeout
 
  }
 
  provisioner "remote-exec" {
    inline = [
      "powershell -File ${var.CC_Install_LogPath}/DATA/InstallCWCOnCC.ps1"
    ]
  }
}    
 
### Upload required components to CC2
#### Set the Provisioner-Connection
resource "null_resource" "UploadRequiredComponentsToCC2" {
 depends_on = [ local_file.InstallCWCOnCC ]
 connection {
    type            = var.Provisioner_Type
    user            = var.Provisioner_Admin-Username
    password        = var.Provisioner_Admin-Password
    host            = data.google_compute_address.IPOfCC2.address
    timeout         = var.Provisioner_Timeout
 
  }
 
###### Upload Cloud Connector configuration file to CC2
  provisioner "file" {
    source      = "${var.CC_Install_LogPath}/DATA/cwc.json"
    destination = "${var.CC_Install_LogPath}/DATA/cwc.json"
   
  }
 
###### Upload SiteID file to CC2
  provisioner "file" {
    source      = "${var.CC_Install_LogPath}/DATA/GetSiteID.txt"
    destination = "${var.CC_Install_LogPath}/DATA/GetSiteID.txt"
   
  }
 
###### Upload ZoneID file to CC2
  provisioner "file" {
    source      = "${var.CC_Install_LogPath}/DATA/GetZoneID.txt"
    destination = "${var.CC_Install_LogPath}/DATA/GetZoneID.txt"
   
  }
 
###### Upload RLID file to CC2
  provisioner "file" {
    source      = "${var.CC_Install_LogPath}/DATA/GetRLID.txt"
    destination = "${var.CC_Install_LogPath}/DATA/GetRLID.txt"
   
  }
 
###### Upload PreReqs script to CC2
  provisioner "file" {
    source      = "${var.CC_Install_LogPath}/DATA/InstallCWCOnCC.ps1"
    destination = "${var.CC_Install_LogPath}/DATA/InstallCWCOnCC.ps1"
   
  }
 
###### Upload Restart script to CC2
  provisioner "file" {
    source      = "${var.CC_Install_LogPath}/DATA/RestartCC.ps1"
    destination = "${var.CC_Install_LogPath}/DATA/RestartCC.ps1"
   
  }
}
 
###### Execute the PreReqs script on CC2
resource "null_resource" "CallRequiredScriptsOnCC2" {
 depends_on = [ null_resource.UploadRequiredComponentsToCC2 ]
 connection {
    type            = var.Provisioner_Type
    user            = var.Provisioner_Admin-Username
    password        = var.Provisioner_Admin-Password
    host            = data.google_compute_address.IPOfCC2.address
    timeout         = var.Provisioner_Timeout
 
  }
 
  provisioner "remote-exec" {
    inline = [
      "powershell -File ${var.CC_Install_LogPath}/DATA/InstallCWCOnCC.ps1"
    ]
  }
}
 
#### Wait 30 mins after CWC creation before restart
resource "time_sleep" "wait_1800_seconds_CC1" {
  depends_on = [ null_resource.CallRequiredScriptsOnCC1 ]
  create_duration = var.Provisioner_Reboot
}
 
#### Wait 30 mins after CWC creation before restart
resource "time_sleep" "wait_1800_seconds_CC2" {
  depends_on = [ null_resource.CallRequiredScriptsOnCC2 ]
  create_duration = var.Provisioner_Reboot
}
 
###### Execute the Reboot script on CC1
resource "null_resource" "CallRebootScriptOnCC1" {
 depends_on = [ time_sleep.wait_1800_seconds_CC1 ]
 connection {
    type            = var.Provisioner_Type
    user            = var.Provisioner_Admin-Username
    password        = var.Provisioner_Admin-Password
    host            = data.google_compute_address.IPOfCC1.address
    timeout         = var.Provisioner_Timeout
 
  }
 
  provisioner "remote-exec" {
    inline = [
      "powershell -File ${var.CC_Install_LogPath}/DATA/RebootCC.ps1"
    ]
  }
}
 
###### Execute the Reboot script on CC2
resource "null_resource" "CallRebootScriptOnCC2" {
 depends_on = [ time_sleep.wait_1800_seconds_CC2 ]
 connection {
    type            = var.Provisioner_Type
    user            = var.Provisioner_Admin-Username
    password        = var.Provisioner_Admin-Password
    host            = data.google_compute_address.IPOfCC2.address
    timeout         = var.Provisioner_Timeout
 
  }
 
  provisioner "remote-exec" {
    inline = [
      "powershell -File ${var.CC_Install_LogPath}/DATA/RebootCC.ps1"
    ]
  }
}

 

Module 3: CConGCP-CitrixCloudStuff

These are the Terraform configuration files for Module 3 (excerpts):

provider.tf

# Terraform deployment of Citrix DaaS on Google Cloud Platform (GCP)
## Definition of all required Terraform providers
 
terraform {
    required_version = ">= 1.7.5"
 
  required_providers {
    restapi = {
      source  = "Mastercard/restapi"
      version = ">=1.18.2"
    }
 
    citrix = {
      source  = "citrix/citrix"
      version = ">=0.5.4"
    }
 
    google = {
      source = "hashicorp/google"
      version = ">=5.21.0"
    }
  }
}
 
# Configure the Google GCP Provider
provider "google" {
  credentials = file(var.CCOnGCP-Creation-Provider-GCPAuthFileJSON)
  project     = var.CCOnGCP-Creation-Provider-GCPProject
  region      = var.CCOnGCP-Creation-Provider-GCPRegion
  zone        = var.CCOnGCP-Creation-Provider-GCPZone
}
 
# Configure the Citrix Provider
provider "citrix" {
  customer_id   = "${var.CC_CustomerID}"  
  client_id     = "${var.CC_APIKey-ClientID}"  
  client_secret = "${var.CC_APIKey-ClientSecret}"  
}


CreateCCEntities.tf

# Terraform deployment of Citrix DaaS on Google Cloud Platform
## Creating all Citrix Cloud-related entities
 
### Creating a Hypervisor Connection
#### Retrieving the ZoneID
data "local_file" "LoadZoneID" {
  filename = "${var.CC_Install_LogPath}/DATA/GetZoneID.txt"
}
 
### Retrieving the GCP credentials
data "local_file" "GCPCredentials" {
  filename = "${var.CC_Install_LogPath}/DATA/tacg-gcp-406812-a8e71b537c99.json"
}
 
#### Creating the Hypervisor Connection
resource "citrix_gcp_hypervisor" "CreateHypervisorConnection" {
  depends_on = [ data.local_file.LoadZoneID ]
    name                        = "${var.CC_GCP-HypConn-Name}"
    zone                        = data.local_file.LoadZoneID.content
    #service_account_id          = "${var.CC_GCP-ServiceAccountID}"
    #service_account_credentials = data.local_file.GCPCredentials.content
    service_account_id          = "${var.tv_client_email}"
    service_account_credentials = "${var.tv_private_key}"
}
 
#### Creating the Hypervisor Resource Pool
resource "citrix_gcp_hypervisor_resource_pool" "CreateHypervisorPool" {
  depends_on = [ citrix_gcp_hypervisor.CreateHypervisorConnection ]
    name                        = "${var.CC_GCP-HypConnPool-Name}"
    hypervisor                  = citrix_gcp_hypervisor.CreateHypervisorConnection.id
    project_name                = "${var.CCOnGCP-CCStuff-Provider-GCPProjectName}"
    region                      = "${var.CCOnGCP-CCStuff-Provider-GCPRegion}"
    vpc                         = "${var.CC_GCP-HypConnPool-VPC}"
    subnets                     = [ "${var.CC_GCP-HypConnPool-Subnet}", ]
}  
 
### Creating a Machine Catalog
#### Retrieving the VPC ID
data "google_compute_network" "GCPVPC" {
  name = "${var.CC_GCP-HypConnPool-VPC}"
}
 
#### Retrieving the Subnet Mask based on the Subnet ID
data "google_compute_network" "GCPSubnet" {
  name = "${var.CC_GCP-HypConnPool-Subnet}"
}
 
#### Sleep 60s to let AWS Background processes settle
resource "time_sleep" "wait_60_seconds" {
  depends_on = [ citrix_gcp_hypervisor_resource_pool.CreateHypervisorPool ]
  create_duration = "60s"
}
 
#### Create the Machine Catalog
resource "citrix_machine_catalog" "CreateMCSCatalog" {
  depends_on            = [ time_sleep.wait_60_seconds ]
    name                        = "${var.CC_GCP-MC-Name}"
    description                 = "${var.CC_GCP-MC-Description}"
    allocation_type             = "${var.CC_GCP-MC-AllocationType}"
    session_support             = "${var.CC_GCP-MC-SessionType}"
    is_power_managed            = true
    is_remote_pc                = false
    provisioning_type           = "MCS"
    zone                        = data.local_file.LoadZoneID.content
    provisioning_scheme         =   {
        hypervisor               = citrix_gcp_hypervisor.CreateHypervisorConnection.id
        hypervisor_resource_pool = citrix_gcp_hypervisor_resource_pool.CreateHypervisorPool.id
        identity_type            = "${var.CC_GCP-MC-IDPType}"
        machine_domain_identity  = {
            domain                   = "${var.CC_GCP-MC-Domain}"
            domain_ou                = "${var.CC_GCP-MC-DomainOU}"
            service_account          = "${var.CC_GCP-MC-DomainAdmin-Username-UPN}"
            service_account_password = "${var.CC_GCP-MC-DomainAdmin-Password}"
        }
        gcp_machine_config = {
            master_image     = "${var.CC_GCP-MC-MasterImage}"
            storage_type     = "${var.CC_GCP-MC-StorageType}"
            #machine_profile  = "${var.CC_GCP-MC-MasterImage}"
        }
        number_of_total_machines =  "${var.CC_GCP-MC-Machine_Count}"
 
        machine_account_creation_rules = {
            naming_scheme      = "${var.CC_GCP-MC-Naming_Scheme_Name}"
            naming_scheme_type = "${var.CC_GCP-MC-Naming_Scheme_Type}"
        }
    }
}
 
#### Sleep 60s to let Citrix Cloud Background processes settle
resource "time_sleep" "wait_60_seconds_1" {
  depends_on = [ citrix_machine_catalog.CreateMCSCatalog ]
  create_duration = "60s"
}  
 
 
#### Create an Example-Policy Set
resource "citrix_policy_set" "SetPolicies" {
  count = var.CC_GCP-Policy-IsNotDaaS ? 1 : 0
  #depends_on                = [ time_sleep.wait_60_seconds_1 ]
    name                    = "${var.CC_GCP-Policy-Name}"
    description             = "${var.CC_GCP-Policy-Description}"
    type                    = "DeliveryGroupPolicies"
    scopes                  = [ "All" ]
 
    policies                = [
                              {
                                  name            = "TACG-GCP-TF-Pol1"
                                  description     = "Policy to enable use of Universal Printer"
                                  is_enabled      = true
                                  policy_settings = [
                                                      {
                                                          name = "UniversalPrintDriverUsage"
                                                          value = "Use universal printing only"
                                                          use_default = false
                                                      },
                                          ]
                                           policy_filters = [
                                                      {
                                                          type = "DesktopGroup"
                                                          is_enabled = true
                                                          is_allowed = true
                                                      },
                                          ]
                                 
                              },
                              {
                                  name            = "TACG-GCP-TF-Pol2"
                                  description     = "Policy to enable Client Drive Redirection"
                                  is_enabled      = true
                                     policy_settings = [
                                                      {
                                                          name = "UniversalPrintDriverUsage"
                                                          value = "Prohibited"
                                                          use_default = false
                                                      },
                                          ]
                                  policy_filters = [
                                                      {
                                                          type = "DesktopGroup"
                                                          is_enabled = true
                                                          is_allowed = true
                                                      },
                                          ]
                              }
                            ]
                  }
 
#Sleep 60s to let Citrix Cloud Background processes settle
resource "time_sleep" "Wait_60_Seconds_2" {
  depends_on = [ citrix_policy_set.SetPolicies ]
  create_duration = "60s"
}
 
#### Create the Delivery Group based on the Machine Catalog
resource "citrix_delivery_group" "CreateDG" {
  depends_on = [ time_sleep.wait_60_seconds_1]
    name                                    = "${var.CC_GCP-DG-Name}"
    associated_machine_catalogs             = [
        {
            machine_catalog                 = citrix_machine_catalog.CreateMCSCatalog.id
            machine_count                   = "${var.CC_GCP-MC-Machine_Count}"
        }
    ]
    desktops                                = [
        {
            published_name                  = "${var.CC_GCP-DG-PublishedDesktopName}"
            description                     = "${var.CC_GCP-DG-Description}"
            restricted_access_users         = {
                                               allow_list = [ "TACG-GCP\\vdaallowed" ]
                                              }
            enabled                         = true
            enable_session_roaming          = var.CC_GCP-DG-SessionRoaming
        }
       
    ]
    autoscale_settings                      = {
            autoscale_enabled                               = true
            disconnect_peak_idle_session_after_seconds      = 300
            log_off_peak_disconnected_session_after_seconds = 300
            peak_log_off_action                             = "Nothing"
            power_time_schemes              = [
                                              {
                                               days_of_week = [
                                                              "Monday",
                                                              "Tuesday",
                                                              "Wednesday",
                                                              "Thursday",
                                                              "Friday"
                                                              ]
                name                        = "${var.CC_GCP-DG-AS-Name}"
                display_name                = "${var.CC_GCP-DG-AS-Name}"
                peak_time_ranges            = [
                                                "09:00-17:00"
                                              ]
                pool_size_schedules         = [
                                               {
                                                 time_range = "09:00-17:00",
                                                 pool_size = 1
                                               }
                                              ]
                pool_using_percentage       = false
            },
        ]
    }
    restricted_access_users                 = {
                                                    allow_list = [ "TACG-GCP\\vdaallowed" ]
                                              }
    reboot_schedules                        = [
                                               {
                                                 name = "TACG-GCP-Reboot Schedule"
                                                 reboot_schedule_enabled = true
                                                 frequency = "Weekly"
                                                 frequency_factor = 1
                                                 days_in_week = [
                                                   "Sunday",
                                                       ]
                                                 start_time = "02:00"
                                                 start_date = "2024-01-01"
                                                 reboot_duration_minutes = 0
                                                 ignore_maintenance_mode = true
                                                 natural_reboot_schedule = false
                                               }
  ]
    policy_set_id = citrix_policy_set.SetPolicies.id
}



 

 

 

 

 


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...