Overview
The aim of this guide is to provide an overview about using Terraform to create a complete Citrix DaaS Resource Location on Microsoft Azure.
At the end of the process you created:
- A new Resource Location (RL) on Azure
- A Domain Controller Virtual Machine and a new Active Directory forest for this Resource Location
- 2 Cloud Controller Virtual Machines registered with the Domain and the Resource Location
- A Hypervisor Connection and a Hypervisor Pool pointing to the new Resource Location in Azure
- A Machine Catalog based on the uploaded Master Image VHD or on an Azure-based Master Image
- A Delivery Group based on the Machine Catalog with full Autoscale Support
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 in a safe and consistent way 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.
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 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.6.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.6.4/terraform_1.6.4_windows_amd64.zip' Progress: 100% - Completed download of C:\TACG\AppData\Local\Temp\chocolatey\terraform\1.6.4\terraform_1.6.4_windows_amd64.zip (24.3 MB). Download of terraform_1.6.4_windows_amd64.zip (24.3 MB) completed. Hashes match. Extracting C:\TACG\AppData\Local\Temp\chocolatey\terraform\1.6.4\terraform_1.6.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 installed successfully:
PS C:\TACG> terraform -version Terraform v1.6.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 providers 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 that 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 an Azure VM does not require to redeploy 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.
Using Terraform with Citrix' provider, you can manage your Citrix products via Infrastructure as Code. Terraform is giving you higher efficiency and consistency on infrastructure management and better reusability on infrastructure configuration.
The provider defines individual units of infrastructure and currently supports both Citrix Virtual Apps and Desktops
and Citrix Desktop as a Service
solutions.
You can automate the creation of a site setup including host connections, machine catalogs, and delivery groups.
You can deploy resources in Azure, AWS, and GCP in addition to 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 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 in it.
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.3.1"... - Installing citrix/citrix v0.3.3... - Installed citrix/citrix v0.3.3 (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:
# Cloud Provider provider "Citrix" { customer_id = "idofthexxxxxxxxx" ‘Citrix Cloud Customer ID client_id = "3edrxxxx-XXXX-XXXX-XXXX-XXXXXXXXXX" ‘ID of the Secure API client planned to use client_secret = "*********************" ‘Secret of the Secure API client planned to use }
A guide for the creation of a secure API client can be found in Citrix Developer Docs and is shown later as well.
# On-Premises Provider provider "Citrix" { hostname = "10.0.0.6" client_id = "foo.local\\admin" client_secret = "foo" }
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 theDomain-Admin Username
.
For Citrix Cloud customers: Use this variable to specifyCloud 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 theDomain-Admin Password
.
For Citrix Cloud customers: Use this variable to specifyCloud 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 totrue
to skip SSL verification only when the target DDC does not have a valid SSL certificate issued by a trusted CA.
When set totrue
, please make sure that your provider config is set for a known DDChostname
.
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 Controllerhostname
.
For Citrix Cloud customers (Optional): Use this variable to override the Citrix DaaS servicehostname
.
Can be set via the Environment Variable CITRIX_HOSTNAME.|
Note:
All information about the Citrix Terraform provider can be found on GitHub.
Example: Create a Resource Group in Azure using Terraform
We create a dedicated Resource Group in Azure to have all resources in one separate place. The Resource Group does not exist before running Terraform:
Before creating the Resource Group, we need to define the needed values in the corresponding Variables file azure-resource-group.auto.tfvars.json
:
Running a test before applying to check if all is correct:
PS C:\TACG> terraform plan 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: # azurerm_resource_group.CC-AZ-TF will be created + resource "azurerm_resource_group" "CC-AZ-TF" { + id = (known after apply) + location = "eastus" + name = "RG-CC-AZ-TF" } Plan: 1 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>
No errors occurred, so we can apply the configuration and create the Resource Group:
PS C:\TACG> terraform apply 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: # azurerm_resource_group.CC-AZ-TF will be created + resource "azurerm_resource_group" "CC-AZ-TF" { + id = (known after apply) + location = "eastus" + name = "RG-CC-AZ-TF" } Plan: 1 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 azurerm_resource_group.CC-AZ-TF: Creating... azurerm_resource_group.CC-AZ-TF: Creation complete after 2s [id=/subscriptions/58xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/RG-CC-AZ-TF] Apply complete! Resources: 1 added, 0 changed, 0 destroyed. PS C:\TACG>
The Resource Group was successfully created:
Deploying a Citrix Cloud Resource location on Microsoft Azure using Terraform
Overview
The aim of this guide is to showcase the possibility of creating a complete Citrix Cloud Resource Location in Azure using Terraform. We want to reduce manual interventions to the absolute minimum.
All Terraform configuration files can be found on GitHub: Insert GitHub link as soon as legal issues are solved
NOTE: For easier reading and understanding, we did not use any
loop
-constructs orcount
keywords - each resource is created explicitly.
The Terraform flow is split into different parts:
-
Part One:
- Creating the initially needed Resources on Azure:
- Creating a dedicated Resource Group on Azure
- Creating a dedicated Storage Account and Storage Container on Azure
- Uploading a pre-configured Master Image used for deploying the Machine Catalog in step 3
- Uploading core components like Citrix Cloud Connector software and Citrix Remote PowerShell SDK to the Storage Container
- Creating the initially needed Resources on Azure:
-
Part Two:
-
Creating three Virtual Machines in the previously created Resource Group:
- One VM, used as a dedicated Domain Controller (DC)
- Two VMs, used as Cloud Controllers (CC)
- Creating a forest on the DC
- Putting the 2 CCs into the new domain
- Installing the needed software on the CCs
-
Creating the necessary Resources in Citrix Cloud:
- Creating a Resource Location in Citrix Cloud
- Configuring the 2 CCs as Cloud Controllers
- Registering the 2 CCs 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 Azure
- 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
- Creating the Machine Catalog and Delivery Group in Citrix Cloud:
NOTE: The first part is separated from the next steps as it is a more or less lengthy process to upload the Master Image. Depending on the available upload bandwidth it can take several hours to complete!
All needed configuration settings are stored in the corresponding Variables which need to be set. Some Configuration settings are propagated throughout the whole Terraform configuration like the Resource Group name, the Resource Group location, Authentication and Authorization settings and so on.
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: CConAzureUpload : |
Filename | Purpose |
---|---|---|
CConAZUpload-provider.tf |
Provider definition and configuration | |
CConAZUpload-main.tf |
Resource configuration and primary flow definition | |
CConAZUpload-main-Variables.tf |
Definition of Variables | |
CConAzUpload-main.auto.tfvars.json |
Setting the values of the Variables | |
CitrixPoshSDK.exe |
Installer for Citrix Remote PowerShell SDK | |
CWCConnector.exe |
Installer for Cloud Connector software | |
Auto-created scripts |
Terraform-created scripts needed for further steps |
Module 2: CConAzureCreation : |
Filename | Purpose |
---|---|---|
CConAzCreate-provider.tf |
Provider definition and configuration | |
CConAzCreate-main.tf |
Resource configuration and primary flow definition | |
CConAzCreate-main-Variables.tf |
Definition of main Variables | |
CConAzCreate-main.auto.tfvars.json |
Import of Variables from previous modules | |
CConAzCreate-main.auto-2.tfvars.json |
Setting the main Variables - Possibility to set Personal settings/needs | |
CConAzCreate-nic-Variables.tf |
Definition of Azure NIC-related Variables | |
CConAzCreate-nic.auto.tfvars.json |
Setting the Azure NIC-related Variables | |
CConAzCreate-nsg-Variables.tf |
Definition of Azure Network Security Group-related Variables | |
CConAzCreate-nsg.auto.tfvars.json |
Setting the Azure Network Security Group-related Variables | |
CConAzCreate-pubip-Variables.tf |
Definition of Azure Public-IP-related Variables | |
CConAzCreate-pubip.auto.tfvars.json |
Setting the Azure Public-IP-related Variables | |
CConAzCreate-resourcegroup-Variables.tf |
Definition of Azure Resource Group-related Variables | |
CConAzCreate-resourcegroup.auto.tfvars.json |
Setting the Azure Resource Group-related Variables | |
CConAzCreate-subnet-Variables.tf |
Definition of Azure Subnet-related Variables | |
CConAzCreate-subnet.auto.tfvars.json |
Setting the Azure Subnet-related Variables | |
CConAzCreate-vm-Variables.tf |
Definition of Azure VM-related Variables | |
CConAzCreate-vm.auto.tfvars.json |
Setting the Azure VM-related Variables | |
CConAzCreate-vnet-Variables.tf |
Definition of Azure vNet-related Variables | |
CConAzCreate-vnet.auto.tfvars.json |
Setting the Azure vNet-related Variables | |
Auto-created scripts |
Terraform-created scripts needed for further steps |
Module 3: CConAzureCitrixCloudStuff : |
Filename | Purpose |
---|---|---|
CConAzCitrixCloudStuff-provider.tf |
Provider definition and configuration | |
CConAzCitrixCloudStuff-hyp.tf |
Hypervisor- and Hypervisor-Pool-Resource configuration | |
CConAzCitrixCloudStuff-hyp-Variables.tf |
Definition of Hypervisor-related Variables | |
CConAzCitrixCloudStuff-hyp.auto.tfvars.json |
Setting of Hypervisor-related Variables | |
CConAzCitrixCloudStuff-main.auto.tfvars.json |
Import of Variables from previous modules | |
CConAzCitrixCloudStuff-mc.tf |
Machine Catalog-Resource configuration | |
CConAzCitrixCloudStuff-mc-Variables.tf |
Definition of Machine Catalog-related Variables | |
CConAzCitrixCloudStuff-mc.auto.tfvars.json |
Setting the Machine Catalog-related Variables | |
CConAzCitrixCloudStuff-dg.tf |
Delivery Group-Resource configuration | |
CConAzCitrixCloudStuff-dg-Variables.tf |
Definition of Delivery Group-related Variables | |
CConAzCitrixCloudStuff-dg.auto.tfvars.json |
Setting the Delivery Group-related Variables | |
CConAzCitrixCloudStuff-zone.tf |
Configuration of the PowerShell-Scripts necessary for retrieving the Zone- and Site-ID | |
GetSiteID.ps1 , GetZoneID.ps1 |
Auto-created PowerShell-Scripts necessary for retrieving the Zone- and Site-ID | |
GetSiteID.txt , GetZoneID.txt |
Files storing the retrieved Zone- and Site-IDs |
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.
The following prerequisites are needed before setting the corresponding settings or running the Terraform workflow to ensure a smooth and error-free build.
Prerequisites
Getting the Azure Resource Locations
The following PowerShell cmdlet lists Azure´s available Resource Locations:
Get-AzLocation | Select Location,Displayname
PS C:\TACG> Get-AzLocation | Select Location,Displayname | Location | DisplayName | |---------------------|----------------------| | eastus | East US | | eastus2 | East US 2 | | southcentralus | South Central US | | westus2 | West US 2 | | westus3 | West US 3 | | australiaeast | Australia East | | southeastasia | Southeast Asia | | northeurope | North Europe | | swedencentral | Sweden Central | | uksouth | UK South | | westeurope | West Europe | | centralus | Central US | | germanywestcentral | Germany West Central | ... PS C:\TACG>
Write down the Location
parameter of the Azure Resource Location that you want to use.
Getting the available Source Images from Azure
We want to automatically deploy the virtual machines necessary for the DC and the CCs - so we need detailed configuration settings:
storage_image_reference { publisher = "Publisher of the Image used to create the VM" offer = "Offer of the Image used to create the VM" sku = "SKU of the Image used to create the VM" version = "Version of the Image used to create the VM" - preferable “latest” }
Some PowerShell scripts help us to list the available Images for Windows Server-based Images:
$location="EastUs" Get-AzVMImagePublisher -Location $location | Where PublisherName -like 'MicrosoftWindows*'
PS C:\TACG> $location="EastUs" PS C:\TACG> Get-AzVMImagePublisher -Location $location | Where PublisherName -like 'MicrosoftWindows*' PublisherName Location Id ------------- -------- -- MicrosoftWindowsDesktop eastus /Subscriptions/58xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/Providers/Microsoft.Compute/Locations/eastus/Publishers/MicrosoftWindowsDesktop MicrosoftWindowsServer eastus /Subscriptions/58xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/Providers/Microsoft.Compute/Locations/eastus/Publishers/MicrosoftWindowsServer MicrosoftWindowsServerHPCPack eastus /Subscriptions/58xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/Providers/Microsoft.Compute/Locations/eastus/Publishers/MicrosoftWindowsServerHPCPack PS C:\TACG>
Note the PublisherName
parameter of the Azure Resource Location that you want to use.
$location="EastUs" $PublisherName="MicrosoftWindowsServer" Get-AzVMImageOffer -Location $location -PublisherName $PublisherName | select offer
PS C:\TACG> $location="EastUs" PS C:\TACG> $PublisherName="MicrosoftWindowsServer" PS C:\TACG> Get-AzVMImageOffer -Location $location -PublisherName $PublisherName | select offer Offer ----- 19h1gen2servertest microsoftserveroperatingsystems-previews servertesting windows-cvm WindowsServer windowsserver-gen2preview windowsserver-previewtest windowsserverdotnet windowsserverhotpatch-previews WindowsServerSemiAnnual windowsserverupgrade PS C:\TACG>
Note the Offer
parameter of the Azure Resource Location that you want to use.
$location="EastUs" $PublisherName="MicrosoftWindowsServer" $OfferName="WindowsServer" Get-AzVMImageSku -Location $location -PublisherName $PublisherName -Offer $OfferName | Select SKUs
PS C:\TACG> $PublisherName="MicrosoftWindowsServer" PS C:\TACG> $OfferName="WindowsServer" PS C:\TACG> $location="EastUs" PS C:\TACG> Get-AzVMImageSku -Location $location -PublisherName $PublisherName -Offer $OfferName | Select SKUs Skus ---- 2008-R2-SP1 2008-R2-SP1-smalldisk 2008-R2-SP1-zhcn 2012-Datacenter 2012-datacenter-gensecond 2012-Datacenter-smalldisk 2012-datacenter-smalldisk-g2 2012-Datacenter-zhcn 2012-datacenter-zhcn-g2 2012-R2-Datacenter 2012-r2-datacenter-gensecond 2012-R2-Datacenter-smalldisk 2012-r2-datacenter-smalldisk-g2 2012-R2-Datacenter-zhcn 2012-r2-datacenter-zhcn-g2 2016-Datacenter 2016-datacenter-gensecond 2016-datacenter-gs 2016-Datacenter-Server-Core 2016-datacenter-server-core-g2 2016-Datacenter-Server-Core-smalldisk 2016-datacenter-server-core-smalldisk-g2 2016-Datacenter-smalldisk 2016-datacenter-smalldisk-g2 2016-Datacenter-with-Containers 2016-datacenter-with-containers-g2 2016-datacenter-with-containers-gs 2016-Datacenter-zhcn 2016-datacenter-zhcn-g2 2019-Datacenter 2019-Datacenter-Core 2019-datacenter-core-g2 2019-Datacenter-Core-smalldisk 2019-datacenter-core-smalldisk-g2 2019-Datacenter-Core-with-Containers 2019-datacenter-core-with-containers-g2 2019-Datacenter-Core-with-Containers-smalldisk 2019-datacenter-core-with-containers-smalldisk-g2 2019-datacenter-gensecond 2019-datacenter-gs 2019-Datacenter-smalldisk 2019-datacenter-smalldisk-g2 2019-Datacenter-with-Containers 2019-datacenter-with-containers-g2 2019-datacenter-with-containers-gs 2019-Datacenter-with-Containers-smalldisk 2019-datacenter-with-containers-smalldisk-g2 2019-Datacenter-zhcn 2019-datacenter-zhcn-g2 2022-datacenter 2022-datacenter-azure-edition 2022-datacenter-azure-edition-core 2022-datacenter-azure-edition-core-smalldisk 2022-datacenter-azure-edition-hotpatch 2022-datacenter-azure-edition-hotpatch-smalldisk 2022-datacenter-azure-edition-smalldisk 2022-datacenter-core 2022-datacenter-core-g2 2022-datacenter-core-smalldisk 2022-datacenter-core-smalldisk-g2 2022-datacenter-g2 2022-datacenter-smalldisk 2022-datacenter-smalldisk-g2 datacenter-core-1809-with-containers-smalldisk-g2 Datacenter-Core-1903-with-Containers-smalldisk datacenter-core-1903-with-containers-smalldisk-g2 datacenter-core-1909-with-containers-smalldisk datacenter-core-1909-with-containers-smalldisk-g2 datacenter-core-2004-with-containers-smalldisk datacenter-core-2004-with-containers-smalldisk-g2 datacenter-core-20h2-with-containers-smalldisk datacenter-core-20h2-with-containers-smalldisk-g2 datacenter-core-20h2-with-containers-smalldisk-gs PS C:\TACG>
Note the Skus
parameter of the Azure Resource Location that you want to use.
Based on all retrieved parameters we can now directly define the Storage_Image_Reference block - in this example we use Windows Server 2022 Azure Edition in the latest available version:
storage_image_reference { publisher = "MicrosoftWindowsServer" offer = "WindowsServer" sku = "2022-datacenter-azure-edition" version = "latest" }
You can also set the corresponding variable in our CConAzCreate-vm.auto.tfvars.json
file:
"azurerm-vm-sourceimage-publisher": "MicrosoftWindowsServer", "azurerm-vm-sourceimage-offer": "WindowsServer", "azurerm-vm-sourceimage-sku": "2022-datacenter-azure-edition", "azurerm-vm-sourceimage-version": "latest"
Getting the available VM sizes from Azure
We need to determine the available VM sizes. A PowerShell script helps us to list the available Images for Windows Server-based Images:
$location="EastUs" Get-AzVMSize -Location $location | Where Name -like 'Standard_D2*' | Select Name,NumberOfCores,MemoryInMB,OSDiskSizeInMB,ResourceDiskSizeInMB | Format-Table
PS C:\TACG> $location="EastUs" PS C:\TACG> Get-AzVMSize -Location $location | Where Name -like 'Standard_D2*' | Select Name,NumberOfCores,MemoryInMB,OSDiskSizeInMB,ResourceDiskSizeInMB | Format-Table Name NumberOfCores MemoryInMB OSDiskSizeInMB ResourceDiskSizeInMB ---- ------------- ---------- -------------- -------------------- Standard_D2_v2 2 7168 1047552 102400 Standard_D2_v2_Promo 2 7168 1047552 102400 Standard_D2_v3 2 8192 1047552 51200 Standard_D2 2 7168 1047552 102400 Standard_D2s_v3 2 8192 1047552 16384 Standard_D2d_v4 2 8192 1047552 76800 Standard_D2_v4 2 8192 1047552 0 Standard_D2ds_v4 2 8192 1047552 76800 Standard_D2s_v4 2 8192 1047552 0 Standard_D2a_v4 2 8192 1047552 51200 Standard_D2as_v4 2 8192 1047552 16384 Standard_D2as_v5 2 8192 1047552 0 Standard_D2ads_v5 2 8192 1047552 76800 Standard_D2ds_v5 2 8192 1047552 76800 Standard_D2d_v5 2 8192 1047552 76800 Standard_D2s_v5 2 8192 1047552 0 Standard_D2_v5 2 8192 1047552 0 Standard_D2ls_v5 2 4096 1047552 0 Standard_D2lds_v5 2 4096 1047552 76800 Standard_D2plds_v5 2 4096 1047552 76800 Standard_D2pls_v5 2 4096 1047552 0 Standard_D2pds_v5 2 8192 1047552 76800 Standard_D2ps_v5 2 8192 1047552 0 PS C:\TACG>
Note the Name
parameter of the Azure Resource Location that 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 Azure 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!
Example:
Checking the vCPU usage and limit for a D2S_v5
VM which we plan to use in this guide:
$Location = 'East US' $VMSize = 'Standard_D2s_v5' $SKU = Get-AzComputeResourceSku -Location $Location | where ResourceType -eq "virtualMachines" | select Name,Family $VMFamily = ($SKU | where Name -eq $VMSize | select -Property Family).Family Get-AzVMUsage -Location $Location | Where-Object { $_.Name.Value -eq $VMFamily }
PS C:\TACG> $Location = 'East US' PS C:\TACG> $VMSize = 'Standard_D2s_v5' PS C:\TACG> $SKU = Get-AzComputeResourceSku -Location $Location | where ResourceType -eq "virtualMachines" | select Name,Family PS C:\TACG> $VMFamily = ($SKU | where Name -eq $VMSize | select -Property Family).Family PS C:\TACG> Get-AzVMUsage -Location $Location | Where-Object { $_.Name.Value -eq $VMFamily } Name Current Value Limit Unit ---- ------------- ----- ---- Standard DSv5 Family vCPUs 10 16 Count PS C:\TACG>
This example shows that we can use a maximum of 3 further D2S_v5
VMs before exceeding the limit.
The vCPU quota or any other quota can be increased using the Azure Console or PowerShell.
More information about increasing vCPU quotas can be found here: https://learn.microsoft.com/en-us/azure/quotas/per-vm-quota-requests
Pre-configured Master Image for deploying the Machine Catalog
Terraform needs a pre-configured Master Image provided in a fixed-type VHD format to create the Machine Catalog.
To convert a VHDX into a fixed-type VHD, use this PowerShell cmdlet:
Convert-VHD -path SourcePath -DestinationPath DestinationPath -VHDType fixed
PS C:\TACG> Convert-VHD -path c:\_hyper-v\w11-mi.vhdx -DestinationPath c:\_hyper-v\w11-min.vhd -VHDType fixed Converting Virtual Disk 44% [ooooooooooooooooooooooooooooooooooooooooooooooo ] PS C:\TACG>
Further Software Components for Configuration and Deployment
Please make sure to put the actual version of the following software components in the Root-directory of Module 1 - CConAzureUpload:
- Citrix Cloud Connector Installer:
cwcconnector.exe
. Download the Citrix Cloud Connector Installer - Citrix Remote PowerShell SDK Installer:
CitrixPoshSdk.exe
. Download the Citrix Remote PowerShell SDK Installer
These components are required during the workflow. The Terraform engine looks for these files.
Creating a Service Principle Name (SPN) with a Client Secret for Azure Authentication
A Service Principal is an application within Azure Active Directory whose authentication tokens can be used as the client_id
, client_secret
, and tenant_id
fields needed by the Azure Terraform provider.
The subscription_id
field can be found in your Azure Account details.
Creation using PowerShell: Use the following commands to create a Service Principal in Azure:
PS C:\TACG> Add-PSSnapin Az.*
PS C:\TACG> Connect-AZaccount Account SubscriptionName TenantId Environment ------- ---------------- -------- ----------- adxxxxxxx@xxxxxx Azure-Abonnement 1 e85XXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX AzureCloud
PS C:\TACG> az ad sp create-for-rbac The output includes credentials that you must protect. Be sure that you do not include these credentials in your code or check the credentials into your source control. For more information, see https://aka.ms/azadsp-cli { "appId": "e9eXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX", "displayName": "azure-cli-2023-10-23-14-19-40", "password": "fFXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", "tenant": "e85XXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX" } PS C:\TACG>
Write down all parameters and put them into the CConAzUpload-main.auto.tfvars.json
file.
… "azurerm-clientid": "e9eXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX", "azurerm-clientsecret": "fFXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", "azurerm-tenantid": "e85XXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX", "azurerm-subscriptionid": "58dXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX", …
Creating a Secure Client in Citrix Cloud
The Secure Client in Citrix Cloud is the same to the SPN in Azure. It is used for Authentication.
API clients in Citrix Cloud are always tied to one administrator and one customer. API clients are not visible to other administrators. If you want to access to more than one customer, you must create API clients within each customer.
API clients are automatically restricted to the rights of that administrator that created it. For example, if an administrator is restricted to access only notifications, then the administrator’s API clients have the same restrictions:
- Reducing an administrator’s access also reduces the access of the API clients owned by that administrator
- Removing an administrator’s access also removes the administrator’s API clients.
To create an API client, select the Identity and Access Management option from the menu. 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.
-
Open
Identity and Access Management
in WebStudio:
-
Click
API Access
,Secure Clients
and put a name in the textbox adjacent to the buttonCreate Client
. After entering a name. clickCreate Client
:
-
After the Secure Client is created, copy and write down the shown
ID
andSecret
:
The Secret is only visible during creation - after closing the window you are not able to 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 CConAzCreate-main-2.auto.tfvars.json
file:
... "cc-customerid": "uzXXXXXXXX", "cc-apikey-clientId": "f4eXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX", "cc-apikey-clientSecret": "VXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", "cc-apikey-type": "client_credentials", ...
Creating a Bearer Token in Citrix Cloud
The Bearer Token is needed for Authorization of some REST-API calls in Citrix Cloud.
As the Citrix provider currently 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:
-
Paste the correct URI into Postman´s address bar and select POST as the method. Verify the correct settings of the API call.
If everything is set correctly, Postman shows a Response containing a JSON-formatted file containing the Bearer token in the fieldaccess-token
: The token is normally valid for3600
seconds. -
Put the access-token value in the
CConAzCreate-main-2.auto.tfvars.json
file:
... "cc-apikey-type": "client_credentials", "cc-apikey-bearer": "CWSAuth bearer=eyJhbGciOiJSUzI1NiIsI...0q0IW7SZFVzeBittWnEwTYOZ7Q" ...
Module 1: Create the initially needed Resources on Microsoft Azure
This module is split into the following configuration parts:
- Creating a dedicated Resource Group on Azure
- Creating a dedicated Storage Account and Storage Container on Azure
- Uploading a pre-configured Master Image used for deploying the Machine Catalog in step 3
- Uploading core components like Citrix Cloud Connector software and Citrix Remote PowerShell SDK to the Storage Container
NOTE: This first part is separated from the next steps as it is a more or less lengthy process to upload the Master Image. Depending on the available upload bandwidth it can take several hours to complete!
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> terraform init Initializing the backend... Initializing provider plug-ins... - Finding hashicorp/azurerm versions matching "3.0.0"... - Finding mastercard/restapi versions matching "1.18.2"... - Finding citrix/citrix versions matching ">= 0.3.5"... - Finding latest version of hashicorp/time... - Finding latest version of hashicorp/local... - Installing hashicorp/azurerm v3.0.0... - Installed hashicorp/azurerm v3.0.0 (signed by HashiCorp) - Installing mastercard/restapi v1.18.2... - Installed mastercard/restapi v1.18.2 (self-signed, key ID DCB8C431D71C30AB) - Installing citrix/citrix v0.3.5... - Installed citrix/citrix v0.3.5 (signed by a HashiCorp partner, key ID 25D62DD8407EA386) - Installing hashicorp/time v0.10.0... - Installed hashicorp/time v0.10.0 (signed by HashiCorp) - Installing hashicorp/local v2.4.1... - Installed hashicorp/local v2.4.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/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> PS C:\TACG> terraform plan 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: # azurerm_resource_group.CC-AZ-TF will be created + resource "azurerm_resource_group" "CC-AZ-TF" { + id = (known after apply) + location = "eastus" + name = "RG-CC-AZ-TF-TEST" } # azurerm_storage_account.CC-AZ-SB will be created + resource "azurerm_storage_account" "CC-AZ-SB" { + access_tier = (known after apply) + account_kind = "StorageV2" + account_replication_type = "LRS" + account_tier = "Standard" + allow_nested_items_to_be_public = true + enable_https_traffic_only = true + id = (known after apply) + infrastructure_encryption_enabled = false + is_hns_enabled = false + large_file_share_enabled = (known after apply) + location = "eastus" + min_tls_version = "TLS1_2" + name = "saxxxxxxxxxxxxxx" + nfsv3_enabled = false + primary_access_key = (sensitive value) + primary_blob_connection_string = (sensitive value) + primary_blob_endpoint = (known after apply) + primary_blob_host = (known after apply) + primary_connection_string = (sensitive value) + primary_dfs_endpoint = (known after apply) + primary_dfs_host = (known after apply) + primary_file_endpoint = (known after apply) + primary_file_host = (known after apply) + primary_location = (known after apply) + primary_queue_endpoint = (known after apply) + primary_queue_host = (known after apply) + primary_table_endpoint = (known after apply) + primary_table_host = (known after apply) + primary_web_endpoint = (known after apply) + primary_web_host = (known after apply) + queue_encryption_key_type = "Service" + resource_group_name = "RG-CC-AZ-TF-TEST" + secondary_access_key = (sensitive value) + secondary_blob_connection_string = (sensitive value) + secondary_blob_endpoint = (known after apply) + secondary_blob_host = (known after apply) + secondary_connection_string = (sensitive value) + secondary_dfs_endpoint = (known after apply) + secondary_dfs_host = (known after apply) + secondary_file_endpoint = (known after apply) + secondary_file_host = (known after apply) + secondary_location = (known after apply) + secondary_queue_endpoint = (known after apply) + secondary_queue_host = (known after apply) + secondary_table_endpoint = (known after apply) + secondary_table_host = (known after apply) + secondary_web_endpoint = (known after apply) + secondary_web_host = (known after apply) + shared_access_key_enabled = true + table_encryption_key_type = "Service" } # azurerm_storage_blob.CC-AZ-SB-CTXPOSHSDK will be created + resource "azurerm_storage_blob" "CC-AZ-SB-CTXPOSHSDK" { + access_tier = (known after apply) + content_type = "application/octet-stream" + id = (known after apply) + metadata = (known after apply) + name = "CitrixPoshSdk.exe" + parallelism = 8 + size = 0 + source = "CitrixPoshSdk.exe" + storage_account_name = "saxxxxxxxxxxxxxx" + storage_container_name = "scsaxxxxxxxxxxxxxx" + type = "Block" + url = (known after apply) } # azurerm_storage_blob.CC-AZ-SB-CWC will be created + resource "azurerm_storage_blob" "CC-AZ-SB-CWC" { + access_tier = (known after apply) + content_type = "application/octet-stream" + id = (known after apply) + metadata = (known after apply) + name = "cwcconnector.exe" + parallelism = 8 + size = 0 + source = "cwcconnector.exe" + storage_account_name = "saxxxxxxxxxxxxxx" + storage_container_name = "scsaxxxxxxxxxxxxxx" + type = "Block" + url = (known after apply) } # azurerm_storage_container.CC-AZ-SB will be created + resource "azurerm_storage_container" "CC-AZ-SB" { + container_access_type = "blob" + has_immutability_policy = (known after apply) + has_legal_hold = (known after apply) + id = (known after apply) + metadata = (known after apply) + name = "scsaxxxxxxxxxxxxxx" + resource_manager_id = (known after apply) + storage_account_name = "saxxxxxxxxxxxxxx" } # local_file.OutputAllVars will be created + resource "local_file" "OutputAllVars" { + content = jsonencode( { + azurerm-clientid = "b1xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + azurerm-clientsecret = "h5xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + azurerm-resource_group_location = "eastus" + azurerm-resource_group_name = "RG-CC-AZ-TF-TEST" + azurerm-storage_account_container_name = "scsaxxxxxxxxxxxxxx" + azurerm-storage_account_location = "eastus" + azurerm-storage_account_name = "saxxxxxxxxxxxxxx" + azurerm-storage_account_replication = "LRS" + azurerm-storage_account_tier = "Standard" + azurerm-subscriptionid = "58xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + azurerm-tenantid = "e8xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + cc-customerid = "uxxxxxxxxxxxxxx" } ) + 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 = "./../__CConAzureCreation/CConAzCreate-main.auto.tfvars.json" + id = (known after apply) } # time_sleep.wait_3_minutes will be created + resource "time_sleep" "wait_3_minutes" { + create_duration = "180s" + id = (known after apply) } # time_sleep.wait_5_minutes will be created + resource "time_sleep" "wait_5_minutes" { + create_duration = "300s" + id = (known after apply) } + resource "azurerm_storage_blob" "CC-AZ-SB-MI" { + access_tier = (known after apply) + content_type = "application/octet-stream" + id = (known after apply) + metadata = (known after apply) + name = "TF-MIN-UP-VHD.vhd" + parallelism = 8 + size = 0 + source = "D:/_PPMM/__TF/__CConAzureUpload/W11-MIN.vhd" + storage_account_name = "saxxxxxxxxxxxxxx" + storage_container_name = "scsaxxxxxxxxxxxxxx" + type = "Block" + url = (known after apply) } Plan: 9 to add, 0 to change, 0 to destroy. PS C:\TACG\_PPMM\__TF\__CConAzureUpload> PS C:\TACG\_PPMM\__TF\__CConAzureUpload> terraform apply 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: # azurerm_resource_group.CC-AZ-TF will be created + resource "azurerm_resource_group" "CC-AZ-TF" { + id = (known after apply) + location = "eastus" + name = "RG-CC-AZ-TF-TEST" } # azurerm_storage_account.CC-AZ-SB will be created + resource "azurerm_storage_account" "CC-AZ-SB" { + access_tier = (known after apply) + account_kind = "StorageV2" + account_replication_type = "LRS" + account_tier = "Standard" + allow_nested_items_to_be_public = true + enable_https_traffic_only = true + id = (known after apply) + infrastructure_encryption_enabled = false + is_hns_enabled = false + large_file_share_enabled = (known after apply) + location = "eastus" + min_tls_version = "TLS1_2" + name = "saxxxxxxxxxxxxxx" + nfsv3_enabled = false + primary_access_key = (sensitive value) + primary_blob_connection_string = (sensitive value) + primary_blob_endpoint = (known after apply) + primary_blob_host = (known after apply) + primary_connection_string = (sensitive value) + primary_dfs_endpoint = (known after apply) + primary_dfs_host = (known after apply) + primary_file_endpoint = (known after apply) + primary_file_host = (known after apply) + primary_location = (known after apply) + primary_queue_endpoint = (known after apply) + primary_queue_host = (known after apply) + primary_table_endpoint = (known after apply) + primary_table_host = (known after apply) + primary_web_endpoint = (known after apply) + primary_web_host = (known after apply) + queue_encryption_key_type = "Service" + resource_group_name = "RG-CC-AZ-TF-TEST" + secondary_access_key = (sensitive value) + secondary_blob_connection_string = (sensitive value) + secondary_blob_endpoint = (known after apply) + secondary_blob_host = (known after apply) + secondary_connection_string = (sensitive value) + secondary_dfs_endpoint = (known after apply) + secondary_dfs_host = (known after apply) + secondary_file_endpoint = (known after apply) + secondary_file_host = (known after apply) + secondary_location = (known after apply) + secondary_queue_endpoint = (known after apply) + secondary_queue_host = (known after apply) + secondary_table_endpoint = (known after apply) + secondary_table_host = (known after apply) + secondary_web_endpoint = (known after apply) + secondary_web_host = (known after apply) + shared_access_key_enabled = true + table_encryption_key_type = "Service" } # azurerm_storage_blob.CC-AZ-SB-CTXPOSHSDK will be created + resource "azurerm_storage_blob" "CC-AZ-SB-CTXPOSHSDK" { + access_tier = (known after apply) + content_type = "application/octet-stream" + id = (known after apply) + metadata = (known after apply) + name = "CitrixPoshSdk.exe" + parallelism = 8 + size = 0 + source = "CitrixPoshSdk.exe" + storage_account_name = "saxxxxxxxxxxxxxx" + storage_container_name = "scsaxxxxxxxxxxxxxx" + type = "Block" + url = (known after apply) } # azurerm_storage_blob.CC-AZ-SB-CWC will be created + resource "azurerm_storage_blob" "CC-AZ-SB-CWC" { + access_tier = (known after apply) + content_type = "application/octet-stream" + id = (known after apply) + metadata = (known after apply) + name = "cwcconnector.exe" + parallelism = 8 + size = 0 + source = "cwcconnector.exe" + storage_account_name = "saxxxxxxxxxxxxxx" + storage_container_name = "scsaxxxxxxxxxxxxxx" + type = "Block" + url = (known after apply) } # azurerm_storage_container.CC-AZ-SB will be created + resource "azurerm_storage_container" "CC-AZ-SB" { + container_access_type = "blob" + has_immutability_policy = (known after apply) + has_legal_hold = (known after apply) + id = (known after apply) + metadata = (known after apply) + name = "scsaxxxxxxxxxxxxxx" + resource_manager_id = (known after apply) + storage_account_name = "saxxxxxxxxxxxxxx" } # local_file.OutputAllVars will be created + resource "local_file" "OutputAllVars" { + content = jsonencode( { + azurerm-clientid = "b1xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + azurerm-clientsecret = "h5xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + azurerm-resource_group_location = "eastus" + azurerm-resource_group_name = "RG-CC-AZ-TF-TEST" + azurerm-storage_account_container_name = "scsaxxxxxxxxxxxxxx" + azurerm-storage_account_location = "eastus" + azurerm-storage_account_name = "saxxxxxxxxxxxxxx" + azurerm-storage_account_replication = "LRS" + azurerm-storage_account_tier = "Standard" + azurerm-subscriptionid = "58xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + azurerm-tenantid = "e8xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + cc-customerid = "uxxxxxxxxxxxxxx" } ) + 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 = "./../__CConAzureCreation/CConAzCreate-main.auto.tfvars.json" + id = (known after apply) } # time_sleep.wait_3_minutes will be created + resource "time_sleep" "wait_3_minutes" { + create_duration = "180s" + id = (known after apply) } # time_sleep.wait_5_minutes will be created + resource "time_sleep" "wait_5_minutes" { + create_duration = "300s" + id = (known after apply) } + resource "azurerm_storage_blob" "CC-AZ-SB-MI" { + access_tier = (known after apply) + content_type = "application/octet-stream" + id = (known after apply) + metadata = (known after apply) + name = "TF-MIN-UP-VHD.vhd" + parallelism = 8 + size = 0 + source = "D:/_PPMM/__TF/__CConAzureUpload/W11-MIN.vhd" + storage_account_name = "saxxxxxxxxxxxxxx" + storage_container_name = "scsaxxxxxxxxxxxxxx" + type = "Block" + url = (known after apply) } ... output removed, as upload lasted more then 20 hours ... Apply complete! Resources: 9 added, 0 changed, 0 destroyed. PS C:\TACG\_PPMM\__TF\__CConAzureUpload>
As no errors occurred, Terraform has completed the creation, configuration, and upload of the relevant prerequisites.
Variables needed in the next module were passed to the next module´s root
-directory as a json
-file.
Example of successful creation:
Creation of the Storage Container on Azure including all required uploads:
Now the next step can be started.
Module 2: Create all Resources in Microsoft Azure and Citrix Cloud
This module is split into the following configuration parts:
-
Creating three Virtual Machines in the previously created Resource Group:
- One VM, used as a dedicated Domain Controller (DC)
- Two VMs, used as Cloud Controllers (CC)
-
Creating a forest on the DC
-
Putting the 2 CCs into the new domain
-
Installing the needed software on the CCs
-
Creating the necessary Resources in Citrix Cloud:
- Creating a Resource Location in Citrix Cloud
- Configuring the 2 CCs as Cloud Controllers
- Registering the 2 CCs in the newly created Resource Location
NOTE: This module has some complexity as many automated actions and many different entities are created on Azure and run on the VMs.
Our provider currently does not support creating a Resource Location on Citrix Cloud. Therefore we use an other provider to create it using a REST-API call.
Please make sure you have configured the variables according to your needs.
NOTE: Each entity can be solely configured using the corresponding
.auto.tfvars.json
file.
Example:
The configuration of the VMs can be set in the CConAzCreate-vm.auto.tfvars.json
file
{ "azurerm-vm-name": "VM-TF-CCCC1", "azurerm-vm-name2": "VM-TF-CCCC2", "azurerm-vm-name-dc": "VM-TF-CC-DC", "azurerm-vm-size": "Standard_D2s_v5", "azurerm-vm-adun": "azxxxxxxxxxxxxxxxxxxxxxxxx", "azurerm-vm-adpw": "!Dxxxxxxxxxxxxxxxxxxxxxxxxx", "azurerm-vm-domainAdminUsername": "tf\\azxxxxxxxxxxxxxxxxxxxxx", "azurerm-vm-domainadmin-pw": "!Dxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "azurerm-vm-domainname": "tf.the-austrian-citrix-guy.at", "azurerm-vm-netbiosname": "tf", "azurerm-vm-nics": [ "NIC1" ], "azurerm-vm-nics2": [ "NIC2" ], "azurerm-vm-nics3": [ "NIC3" ], "azurerm-vm-osdisk-name": "OsDisk1", "azurerm-vm-osdisk-name2": "OsDisk2", "azurerm-vm-osdisk-name-dc": "OsDisk-DC", "azurerm-vm-osdisk-cache": "ReadWrite", "azurerm-vm-osdisk-storagetype": "Standard_LRS", "azurerm-vm-osdisk-size": "120", "azurerm-vm-osdisk-createfrom": "FromImage", "azurerm-vm-sourceimage-publisher": "MicrosoftWindowsServer", "azurerm-vm-sourceimage-offer": "WindowsServer", "azurerm-vm-sourceimage-sku": "2022-datacenter-azure-edition", "azurerm-vm-sourceimage-version": "latest" }
The Terraform configuration contains some idle time slots to make sure that background operations on Azure or on the VMs can completed before the next configuration steps occur.
We have seen different elapsed configuration times related to different loads on the Azure systems!
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\__CConAzureCreation> terraform init Initializing the backend... Initializing provider plug-ins... - terraform.io/builtin/terraform is built in to Terraform - Finding latest version of hashicorp/time... - Finding latest version of hashicorp/local... - Finding latest version of hashicorp/random... - Finding latest version of hashicorp/http... - Finding mastercard/restapi versions matching "1.18.2"... - Finding citrix/citrix versions matching ">= 0.3.5"... - Finding hashicorp/azurerm versions matching "3.0.0"... - Installing hashicorp/time v0.10.0... - Installed hashicorp/time v0.10.0 (signed by HashiCorp) - Installing hashicorp/local v2.4.1... - Installed hashicorp/local v2.4.1 (signed by HashiCorp) - Installing hashicorp/random v3.6.0... - Installed hashicorp/random v3.6.0 (signed by HashiCorp) - Installing hashicorp/http v3.4.1... - Installed hashicorp/http v3.4.1 (signed by HashiCorp) - Installing mastercard/restapi v1.18.2... - Installed mastercard/restapi v1.18.2 (self-signed, key ID DCB8C431D71C30AB) - Installing citrix/citrix v0.3.5... - Installed citrix/citrix v0.3.5 (signed by a HashiCorp partner, key ID 25D62DD8407EA386) - Installing hashicorp/azurerm v3.0.0... - Installed hashicorp/azurerm v3.0.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/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\__CConAzureCreation> terraform plan 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: # azurerm_managed_disk.CreateManagedDiskFromUploadedVHD will be created + resource "azurerm_managed_disk" "CreateManagedDiskFromUploadedVHD" { + create_option = "Import" + disk_iops_read_only = (known after apply) + disk_iops_read_write = (known after apply) + disk_mbps_read_only = (known after apply) + disk_mbps_read_write = (known after apply) + disk_size_gb = (known after apply) + hyper_v_generation = "V2" + id = (known after apply) + location = "eastus" + logical_sector_size = (known after apply) + max_shares = (known after apply) + name = "w11-min.vhd" + os_type = "Windows" + public_network_access_enabled = true + resource_group_name = "RG-CC-AZ-TF-TEST" + source_uri = "https://saxxxxxxxxxxxxxx.blob.core.windows.net/scsaxxxxxxxxxxxxxx/w11-min.vhd" + storage_account_id = "/subscriptions/58xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/RG-CC-AZ-TF-TEST/providers/Microsoft.Storage/storageAccounts/saxxxxxxxxxxxxxx" + storage_account_type = "Standard_LRS" + tier = (known after apply) } # azurerm_network_interface.CC-AZ-TF will be created + resource "azurerm_network_interface" "CC-AZ-TF" { + applied_dns_servers = (known after apply) + dns_servers = (known after apply) + enable_accelerated_networking = false + enable_ip_forwarding = false + id = (known after apply) + internal_dns_name_label = (known after apply) + internal_domain_name_suffix = (known after apply) + location = "eastus" + mac_address = (known after apply) + name = "NIC-TF-AZ-CC1" + private_ip_address = (known after apply) + private_ip_addresses = (known after apply) + resource_group_name = "RG-CC-AZ-TF-TEST" + virtual_machine_id = (known after apply) + ip_configuration { + gateway_load_balancer_frontend_ip_configuration_id = (known after apply) + name = "IP-TF-AZ-CC1" + primary = (known after apply) + private_ip_address = (known after apply) + private_ip_address_allocation = "Dynamic" + private_ip_address_version = "IPv4" + public_ip_address_id = (known after apply) + subnet_id = (known after apply) } } # azurerm_network_interface.CC-AZ-TF-DC will be created + resource "azurerm_network_interface" "CC-AZ-TF-DC" { + applied_dns_servers = (known after apply) + dns_servers = [ + "10.0.0.4", ] + enable_accelerated_networking = false + enable_ip_forwarding = false + id = (known after apply) + internal_dns_name_label = (known after apply) + internal_domain_name_suffix = (known after apply) + location = "eastus" + mac_address = (known after apply) + name = "NIC-TF-AZ-DC" + private_ip_address = (known after apply) + private_ip_addresses = (known after apply) + resource_group_name = "RG-CC-AZ-TF-TEST" + virtual_machine_id = (known after apply) + ip_configuration { + gateway_load_balancer_frontend_ip_configuration_id = (known after apply) + name = "IP-TF-AZ-DC" + primary = (known after apply) + private_ip_address = (known after apply) + private_ip_address_allocation = "Dynamic" + private_ip_address_version = "IPv4" + public_ip_address_id = (known after apply) + subnet_id = (known after apply) } } # azurerm_network_interface.CC-AZ-TF2 will be created + resource "azurerm_network_interface" "CC-AZ-TF2" { + applied_dns_servers = (known after apply) + dns_servers = (known after apply) + enable_accelerated_networking = false + enable_ip_forwarding = false + id = (known after apply) + internal_dns_name_label = (known after apply) + internal_domain_name_suffix = (known after apply) + location = "eastus" + mac_address = (known after apply) + name = "NIC-TF-AZ-CC2" + private_ip_address = (known after apply) + private_ip_addresses = (known after apply) + resource_group_name = "RG-CC-AZ-TF-TEST" + virtual_machine_id = (known after apply) + ip_configuration { + gateway_load_balancer_frontend_ip_configuration_id = (known after apply) + name = "IP-TF-AZ-CC2" + primary = (known after apply) + private_ip_address = (known after apply) + private_ip_address_allocation = "Dynamic" + private_ip_address_version = "IPv4" + public_ip_address_id = (known after apply) + subnet_id = (known after apply) } } # azurerm_network_security_group.CC-AZ-TF will be created + resource "azurerm_network_security_group" "CC-AZ-TF" { + id = (known after apply) + location = "eastus" + name = "NSG-TF-AZ-CC" + resource_group_name = "RG-CC-AZ-TF-TEST" + security_rule = [ + { + access = "Allow" + description = "" + destination_address_prefix = "*" + destination_address_prefixes = [] + destination_application_security_group_ids = [] + destination_port_range = "3389" + destination_port_ranges = [] + direction = "Inbound" + name = "RDP" + priority = 100 + protocol = "Tcp" + source_address_prefix = "*" + source_address_prefixes = [] + source_application_security_group_ids = [] + source_port_range = "*" + source_port_ranges = [] }, + { + access = "Allow" + description = "" + destination_address_prefix = "*" + destination_address_prefixes = [] + destination_application_security_group_ids = [] + destination_port_range = "443" + destination_port_ranges = [] + direction = "Inbound" + name = "HTTPS" + priority = 130 + protocol = "Tcp" + source_address_prefix = "*" + source_address_prefixes = [] + source_application_security_group_ids = [] + source_port_range = "*" + source_port_ranges = [] }, + { + access = "Allow" + description = "" + destination_address_prefix = "*" + destination_address_prefixes = [] + destination_application_security_group_ids = [] + destination_port_range = "5985" + destination_port_ranges = [] + direction = "Inbound" + name = "WinRM" + priority = 110 + protocol = "Tcp" + source_address_prefix = "*" + source_address_prefixes = [] + source_application_security_group_ids = [] + source_port_range = "*" + source_port_ranges = [] }, + { + access = "Allow" + description = "" + destination_address_prefix = "*" + destination_address_prefixes = [] + destination_application_security_group_ids = [] + destination_port_range = "80" + destination_port_ranges = [] + direction = "Inbound" + name = "HTTP" + priority = 120 + protocol = "Tcp" + source_address_prefix = "*" + source_address_prefixes = [] + source_application_security_group_ids = [] + source_port_range = "*" + source_port_ranges = [] }, ] } # azurerm_public_ip.CC-AZ-TF will be created + resource "azurerm_public_ip" "CC-AZ-TF" { + allocation_method = "Static" + fqdn = (known after apply) + id = (known after apply) + idle_timeout_in_minutes = 4 + ip_address = (known after apply) + ip_version = "IPv4" + location = "eastus" + name = "PUBIP-TF-AZ-CC1" + resource_group_name = "RG-CC-AZ-TF-TEST" + sku = "Basic" + sku_tier = "Regional" } # azurerm_public_ip.CC-AZ-TF-DC will be created + resource "azurerm_public_ip" "CC-AZ-TF-DC" { + allocation_method = "Static" + fqdn = (known after apply) + id = (known after apply) + idle_timeout_in_minutes = 4 + ip_address = (known after apply) + ip_version = "IPv4" + location = "eastus" + name = "PUBIP-TF-AZ-CC-DC" + resource_group_name = "RG-CC-AZ-TF-TEST" + sku = "Basic" + sku_tier = "Regional" } # azurerm_public_ip.CC-AZ-TF2 will be created + resource "azurerm_public_ip" "CC-AZ-TF2" { + allocation_method = "Static" + fqdn = (known after apply) + id = (known after apply) + idle_timeout_in_minutes = 4 + ip_address = (known after apply) + ip_version = "IPv4" + location = "eastus" + name = "PUBIP-TF-AZ-CC2" + resource_group_name = "RG-CC-AZ-TF-TEST" + sku = "Basic" + sku_tier = "Regional" } # azurerm_storage_blob.CC-AZ-SB-DL will be created + resource "azurerm_storage_blob" "CC-AZ-SB-DL" { + access_tier = (known after apply) + content_type = "application/octet-stream" + id = (known after apply) + metadata = (known after apply) + name = "download.ps1" + parallelism = 8 + size = 0 + source = "download.ps1" + storage_account_name = "saxxxxxxxxxxxxxx" + storage_container_name = "scsaxxxxxxxxxxxxxx" + type = "Block" + url = (known after apply) } # azurerm_storage_blob.CC-AZ-SB-Install will be created + resource "azurerm_storage_blob" "CC-AZ-SB-Install" { + access_tier = (known after apply) + content_type = "application/octet-stream" + id = (known after apply) + metadata = (known after apply) + name = "InstallCWC.ps1" + parallelism = 8 + size = 0 + source = "InstallCWC.ps1" + storage_account_name = "saxxxxxxxxxxxxxx" + storage_container_name = "scsaxxxxxxxxxxxxxx" + type = "Block" + url = (known after apply) } # azurerm_storage_blob.CC-AZ-SB-InstallPreReqs-ps1 will be created + resource "azurerm_storage_blob" "CC-AZ-SB-InstallPreReqs-ps1" { + access_tier = (known after apply) + content_type = "application/octet-stream" + id = (known after apply) + metadata = (known after apply) + name = "InstallPreReqs.ps1" + parallelism = 8 + size = 0 + source = "InstallPreReqs.ps1" + storage_account_name = "saxxxxxxxxxxxxxx" + storage_container_name = "scsaxxxxxxxxxxxxxx" + type = "Block" + url = (known after apply) } # azurerm_storage_blob.CC-AZ-SB-JSON will be created + resource "azurerm_storage_blob" "CC-AZ-SB-JSON" { + access_tier = (known after apply) + content_type = "application/octet-stream" + id = (known after apply) + metadata = (known after apply) + name = "cwc.json" + parallelism = 8 + size = 0 + source = "cwc.json" + storage_account_name = "saxxxxxxxxxxxxxx" + storage_container_name = "scsaxxxxxxxxxxxxxx" + type = "Block" + url = (known after apply) } # azurerm_subnet.CC-AZ-TF will be created + resource "azurerm_subnet" "CC-AZ-TF" { + address_prefixes = [ + "10.0.0.0/24", ] + enforce_private_link_endpoint_network_policies = false + enforce_private_link_service_network_policies = false + id = (known after apply) + name = "SN-TF-AZ" + resource_group_name = "RG-CC-AZ-TF-TEST" + virtual_network_name = "VNET-TF-AZ-CC1" } # azurerm_subnet_network_security_group_association.CC-AZ-TF will be created + resource "azurerm_subnet_network_security_group_association" "CC-AZ-TF" { + id = (known after apply) + network_security_group_id = (known after apply) + subnet_id = (known after apply) } # azurerm_virtual_machine_extension.DownloadPSScriptToLocalVMAndExecuteItOnCC1 will be created + resource "azurerm_virtual_machine_extension" "DownloadPSScriptToLocalVMAndExecuteItOnCC1" { + id = (known after apply) + name = "DownloadPSScriptToLocalVMAndExecuteIt" + publisher = "Microsoft.Compute" + settings = <<-EOT { "commandToExecute": "powershell.exe -ExecutionPolicy Unrestricted -Command \"Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force; Install-Module -Name PowerShellGet -Force; Invoke-WebRequest -Uri https://saxxxxxxxxxxxxxx.blob.core.windows.net/scsaxxxxxxxxxxxxxx/download.ps1 -OutFile c:/temp/download.ps1; c:/temp/download.ps1; c:/temp/InstallPreReqs.ps1\"" } EOT + type = "CustomScriptExtension" + type_handler_version = "1.9" + virtual_machine_id = (known after apply) } # azurerm_virtual_machine_extension.DownloadPSScriptToLocalVMAndExecuteItOnCC2 will be created + resource "azurerm_virtual_machine_extension" "DownloadPSScriptToLocalVMAndExecuteItOnCC2" { + id = (known after apply) + name = "DownloadPSScriptToLocalVMAndExecuteIt" + publisher = "Microsoft.Compute" + settings = <<-EOT { "commandToExecute": "powershell.exe -ExecutionPolicy Unrestricted -Command \"Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force; Install-Module -Name PowerShellGet -Force; Invoke-WebRequest -Uri https://saxxxxxxxxxxxxxx.blob.core.windows.net/scsaxxxxxxxxxxxxxx/download.ps1 -OutFile c:/temp/download.ps1; c:/temp/download.ps1; c:/temp/InstallPreReqs.ps1\"" } EOT + type = "CustomScriptExtension" + type_handler_version = "1.9" + virtual_machine_id = (known after apply) } # azurerm_virtual_machine_extension.create-active-directory-forest will be created + resource "azurerm_virtual_machine_extension" "create-active-directory-forest" { + id = (known after apply) + name = "create-active-directory-forest" + publisher = "Microsoft.Compute" + settings = (sensitive value) + type = "CustomScriptExtension" + type_handler_version = "1.9" + virtual_machine_id = (known after apply) } # azurerm_virtual_network.CC-AZ-TF will be created + resource "azurerm_virtual_network" "CC-AZ-TF" { + address_space = [ + "10.0.0.0/16", ] + dns_servers = (known after apply) + guid = (known after apply) + id = (known after apply) + location = "eastus" + name = "VNET-TF-AZ-CC1" + resource_group_name = "RG-CC-AZ-TF-TEST" + subnet = (known after apply) } # azurerm_virtual_network_dns_servers.CC-AZ-TF will be created + resource "azurerm_virtual_network_dns_servers" "CC-AZ-TF" { + dns_servers = [ + "10.0.0.4", ] + id = (known after apply) + virtual_network_id = (known after apply) } # azurerm_windows_virtual_machine.CC-AZ-TF will be created + resource "azurerm_windows_virtual_machine" "CC-AZ-TF" { + admin_password = (sensitive value) + admin_username = "azxxxxxx" + allow_extension_operations = true + computer_name = (known after apply) + enable_automatic_updates = true + extensions_time_budget = "PT1H30M" + hotpatching_enabled = false + id = (known after apply) + location = "eastus" + max_bid_price = -1 + name = "VM-TF-CCCC1" + network_interface_ids = (known after apply) + patch_mode = "AutomaticByOS" + platform_fault_domain = -1 + priority = "Regular" + private_ip_address = (known after apply) + private_ip_addresses = (known after apply) + provision_vm_agent = true + public_ip_address = (known after apply) + public_ip_addresses = (known after apply) + resource_group_name = "RG-CC-AZ-TF-TEST" + size = "Standard_D2s_v5" + virtual_machine_id = (known after apply) + os_disk { + caching = "ReadWrite" + disk_size_gb = (known after apply) + name = "OsDisk1" + storage_account_type = "Standard_LRS" + write_accelerator_enabled = false } + source_image_reference { + offer = "WindowsServer" + publisher = "MicrosoftWindowsServer" + sku = "2022-datacenter-azure-edition" + version = "latest" } } # azurerm_windows_virtual_machine.CC-AZ-TF-DC will be created + resource "azurerm_windows_virtual_machine" "CC-AZ-TF-DC" { + admin_password = (sensitive value) + admin_username = "azxxxxxx" + allow_extension_operations = true + computer_name = (known after apply) + enable_automatic_updates = true + extensions_time_budget = "PT1H30M" + hotpatching_enabled = false + id = (known after apply) + location = "eastus" + max_bid_price = -1 + name = "VM-TF-CC-DC" + network_interface_ids = (known after apply) + patch_mode = "AutomaticByOS" + platform_fault_domain = -1 + priority = "Regular" + private_ip_address = (known after apply) + private_ip_addresses = (known after apply) + provision_vm_agent = true + public_ip_address = (known after apply) + public_ip_addresses = (known after apply) + resource_group_name = "RG-CC-AZ-TF-TEST" + size = "Standard_D2s_v5" + virtual_machine_id = (known after apply) + os_disk { + caching = "ReadWrite" + disk_size_gb = (known after apply) + name = "OsDisk-DC" + storage_account_type = "Standard_LRS" + write_accelerator_enabled = false } + source_image_reference { + offer = "WindowsServer" + publisher = "MicrosoftWindowsServer" + sku = "2022-datacenter-azure-edition" + version = "latest" } } # azurerm_windows_virtual_machine.CC-AZ-TF2 will be created + resource "azurerm_windows_virtual_machine" "CC-AZ-TF2" { + admin_password = (sensitive value) + admin_username = "azxxxxxx" + allow_extension_operations = true + computer_name = (known after apply) + enable_automatic_updates = true + extensions_time_budget = "PT1H30M" + hotpatching_enabled = false + id = (known after apply) + location = "eastus" + max_bid_price = -1 + name = "VM-TF-CCCC2" + network_interface_ids = (known after apply) + patch_mode = "AutomaticByOS" + platform_fault_domain = -1 + priority = "Regular" + private_ip_address = (known after apply) + private_ip_addresses = (known after apply) + provision_vm_agent = true + public_ip_address = (known after apply) + public_ip_addresses = (known after apply) + resource_group_name = "RG-CC-AZ-TF-TEST" + size = "Standard_D2s_v5" + virtual_machine_id = (known after apply) + os_disk { + caching = "ReadWrite" + disk_size_gb = (known after apply) + name = "OsDisk2" + storage_account_type = "Standard_LRS" + write_accelerator_enabled = false } + source_image_reference { + offer = "WindowsServer" + publisher = "MicrosoftWindowsServer" + sku = "2022-datacenter-azure-edition" + version = "latest" } } # 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 = "./cwc.json" + id = (known after apply) } # local_file.Download-ps1 will be created + resource "local_file" "Download-ps1" { + content = <<-EOT Install-Module -Name Az -Repository PSGallery -Force Start-Sleep -Seconds 180 $cont = New-AzStorageContext -StorageAccountName saxxxxxxxxxxxxxx -Anonymous -Protocol https Get-AzStorageBlobContent -blob cwcconnector.exe -container scsaxxxxxxxxxxxxxx -Context $cont -destination c:/temp Get-AzStorageBlobContent -blob cwc.json -container scsaxxxxxxxxxxxxxx -Context $cont -destination c:/temp Get-AzStorageBlobContent -blob InstallPreReqs.ps1 -container scsaxxxxxxxxxxxxxx -Context $cont -destination c:/temp Get-AzStorageBlobContent -blob InstallCWC.ps1 -container scsaxxxxxxxxxxxxxx -Context $cont -destination c:/temp Get-AzStorageBlobContent -blob CitrixPoshSdk.exe -container scsaxxxxxxxxxxxxxx -Context $cont -destination c:/temp EOT + 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 = "./download.ps1" + id = (known after apply) } # local_file.InstallCWC-ps1 will be created + resource "local_file" "InstallCWC-ps1" { + content = <<-EOT $CtxPoSHSDK = Get-WmiObject -Class Win32_Product | Where-Object{$_.Name -Like "Citrix*"} if ($CtxPoSHSDK -ne $null) { $CtxPoSHSDK.Uninstall() } cd /temp ./CWCConnector.exe /q /ParametersFilePath:c:/temp/cwc.json EOT + 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 = "./InstallCWC.ps1" + id = (known after apply) } # local_file.InstallPreReqs-ps1 will be created + resource "local_file" "InstallPreReqs-ps1" { + 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 = "./InstallPreReqs.ps1" + id = (known after apply) } # random_uuid.IDforCCRL will be created + resource "random_uuid" "IDforCCRL" { + id = (known after apply) + result = (known after apply) } # restapi_object.CreateRL will be created + resource "restapi_object" "CreateRL" { + api_data = (known after apply) + api_response = (known after apply) + create_response = (known after apply) + data = (known after apply) + id = (known after apply) + path = "/resourcelocations" } # terraform_data.Download-CWC-Installer will be created + resource "terraform_data" "Download-CWC-Installer" { + id = (known after apply) } # time_sleep.wait_1_minutes will be created + resource "time_sleep" "wait_1_minutes" { + create_duration = "60s" + id = (known after apply) } # time_sleep.wait_2_minutes_after_adcompleted will be created + resource "time_sleep" "wait_2_minutes_after_adcompleted" { + create_duration = "120s" + id = (known after apply) } # time_sleep.wait_3_minutes will be created + resource "time_sleep" "wait_3_minutes" { + create_duration = "180s" + id = (known after apply) } # time_sleep.wait_4_minutes will be created + resource "time_sleep" "wait_4_minutes" { + create_duration = "240s" + id = (known after apply) } # time_sleep.wait_5_minutes will be created + resource "time_sleep" "wait_5_minutes" { + create_duration = "300s" + id = (known after apply) } Plan: 34 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\__CConAzureCreation> terraform apply 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: # azurerm_managed_disk.CreateManagedDiskFromUploadedVHD will be created + resource "azurerm_managed_disk" "CreateManagedDiskFromUploadedVHD" { + create_option = "Import" + disk_iops_read_only = (known after apply) + disk_iops_read_write = (known after apply) + disk_mbps_read_only = (known after apply) + disk_mbps_read_write = (known after apply) + disk_size_gb = (known after apply) + hyper_v_generation = "V2" + id = (known after apply) + location = "eastus" + logical_sector_size = (known after apply) + max_shares = (known after apply) + name = "w11-min.vhd" + os_type = "Windows" + public_network_access_enabled = true + resource_group_name = "RG-CC-AZ-TF-TEST" + source_uri = "https://saxxxxxxxxxxxxxx.blob.core.windows.net/scsaxxxxxxxxxxxxxx/w11-min.vhd" + storage_account_id = "/subscriptions/58xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/RG-CC-AZ-TF-TEST/providers/Microsoft.Storage/storageAccounts/saxxxxxxxxxxxxxx" + storage_account_type = "Standard_LRS" + tier = (known after apply) } # azurerm_network_interface.CC-AZ-TF will be created + resource "azurerm_network_interface" "CC-AZ-TF" { + applied_dns_servers = (known after apply) + dns_servers = (known after apply) + enable_accelerated_networking = false + enable_ip_forwarding = false + id = (known after apply) + internal_dns_name_label = (known after apply) + internal_domain_name_suffix = (known after apply) + location = "eastus" + mac_address = (known after apply) + name = "NIC-TF-AZ-CC1" + private_ip_address = (known after apply) + private_ip_addresses = (known after apply) + resource_group_name = "RG-CC-AZ-TF-TEST" + virtual_machine_id = (known after apply) + ip_configuration { + gateway_load_balancer_frontend_ip_configuration_id = (known after apply) + name = "IP-TF-AZ-CC1" + primary = (known after apply) + private_ip_address = (known after apply) + private_ip_address_allocation = "Dynamic" + private_ip_address_version = "IPv4" + public_ip_address_id = (known after apply) + subnet_id = (known after apply) } } # azurerm_network_interface.CC-AZ-TF-DC will be created + resource "azurerm_network_interface" "CC-AZ-TF-DC" { + applied_dns_servers = (known after apply) + dns_servers = [ + "10.0.0.4", ] + enable_accelerated_networking = false + enable_ip_forwarding = false + id = (known after apply) + internal_dns_name_label = (known after apply) + internal_domain_name_suffix = (known after apply) + location = "eastus" + mac_address = (known after apply) + name = "NIC-TF-AZ-DC" + private_ip_address = (known after apply) + private_ip_addresses = (known after apply) + resource_group_name = "RG-CC-AZ-TF-TEST" + virtual_machine_id = (known after apply) + ip_configuration { + gateway_load_balancer_frontend_ip_configuration_id = (known after apply) + name = "IP-TF-AZ-DC" + primary = (known after apply) + private_ip_address = (known after apply) + private_ip_address_allocation = "Dynamic" + private_ip_address_version = "IPv4" + public_ip_address_id = (known after apply) + subnet_id = (known after apply) } } # azurerm_network_interface.CC-AZ-TF2 will be created + resource "azurerm_network_interface" "CC-AZ-TF2" { + applied_dns_servers = (known after apply) + dns_servers = (known after apply) + enable_accelerated_networking = false + enable_ip_forwarding = false + id = (known after apply) + internal_dns_name_label = (known after apply) + internal_domain_name_suffix = (known after apply) + location = "eastus" + mac_address = (known after apply) + name = "NIC-TF-AZ-CC2" + private_ip_address = (known after apply) + private_ip_addresses = (known after apply) + resource_group_name = "RG-CC-AZ-TF-TEST" + virtual_machine_id = (known after apply) + ip_configuration { + gateway_load_balancer_frontend_ip_configuration_id = (known after apply) + name = "IP-TF-AZ-CC2" + primary = (known after apply) + private_ip_address = (known after apply) + private_ip_address_allocation = "Dynamic" + private_ip_address_version = "IPv4" + public_ip_address_id = (known after apply) + subnet_id = (known after apply) } } # azurerm_network_security_group.CC-AZ-TF will be created + resource "azurerm_network_security_group" "CC-AZ-TF" { + id = (known after apply) + location = "eastus" + name = "NSG-TF-AZ-CC" + resource_group_name = "RG-CC-AZ-TF-TEST" + security_rule = [ + { + access = "Allow" + description = "" + destination_address_prefix = "*" + destination_address_prefixes = [] + destination_application_security_group_ids = [] + destination_port_range = "3389" + destination_port_ranges = [] + direction = "Inbound" + name = "RDP" + priority = 100 + protocol = "Tcp" + source_address_prefix = "*" + source_address_prefixes = [] + source_application_security_group_ids = [] + source_port_range = "*" + source_port_ranges = [] }, + { + access = "Allow" + description = "" + destination_address_prefix = "*" + destination_address_prefixes = [] + destination_application_security_group_ids = [] + destination_port_range = "443" + destination_port_ranges = [] + direction = "Inbound" + name = "HTTPS" + priority = 130 + protocol = "Tcp" + source_address_prefix = "*" + source_address_prefixes = [] + source_application_security_group_ids = [] + source_port_range = "*" + source_port_ranges = [] }, + { + access = "Allow" + description = "" + destination_address_prefix = "*" + destination_address_prefixes = [] + destination_application_security_group_ids = [] + destination_port_range = "5985" + destination_port_ranges = [] + direction = "Inbound" + name = "WinRM" + priority = 110 + protocol = "Tcp" + source_address_prefix = "*" + source_address_prefixes = [] + source_application_security_group_ids = [] + source_port_range = "*" + source_port_ranges = [] }, + { + access = "Allow" + description = "" + destination_address_prefix = "*" + destination_address_prefixes = [] + destination_application_security_group_ids = [] + destination_port_range = "80" + destination_port_ranges = [] + direction = "Inbound" + name = "HTTP" + priority = 120 + protocol = "Tcp" + source_address_prefix = "*" + source_address_prefixes = [] + source_application_security_group_ids = [] + source_port_range = "*" + source_port_ranges = [] }, ] } # azurerm_public_ip.CC-AZ-TF will be created + resource "azurerm_public_ip" "CC-AZ-TF" { + allocation_method = "Static" + fqdn = (known after apply) + id = (known after apply) + idle_timeout_in_minutes = 4 + ip_address = (known after apply) + ip_version = "IPv4" + location = "eastus" + name = "PUBIP-TF-AZ-CC1" + resource_group_name = "RG-CC-AZ-TF-TEST" + sku = "Basic" + sku_tier = "Regional" } # azurerm_public_ip.CC-AZ-TF-DC will be created + resource "azurerm_public_ip" "CC-AZ-TF-DC" { + allocation_method = "Static" + fqdn = (known after apply) + id = (known after apply) + idle_timeout_in_minutes = 4 + ip_address = (known after apply) + ip_version = "IPv4" + location = "eastus" + name = "PUBIP-TF-AZ-CC-DC" + resource_group_name = "RG-CC-AZ-TF-TEST" + sku = "Basic" + sku_tier = "Regional" } # azurerm_public_ip.CC-AZ-TF2 will be created + resource "azurerm_public_ip" "CC-AZ-TF2" { + allocation_method = "Static" + fqdn = (known after apply) + id = (known after apply) + idle_timeout_in_minutes = 4 + ip_address = (known after apply) + ip_version = "IPv4" + location = "eastus" + name = "PUBIP-TF-AZ-CC2" + resource_group_name = "RG-CC-AZ-TF-TEST" + sku = "Basic" + sku_tier = "Regional" } # azurerm_storage_blob.CC-AZ-SB-DL will be created + resource "azurerm_storage_blob" "CC-AZ-SB-DL" { + access_tier = (known after apply) + content_type = "application/octet-stream" + id = (known after apply) + metadata = (known after apply) + name = "download.ps1" + parallelism = 8 + size = 0 + source = "download.ps1" + storage_account_name = "saxxxxxxxxxxxxxx" + storage_container_name = "scsaxxxxxxxxxxxxxx" + type = "Block" + url = (known after apply) } # azurerm_storage_blob.CC-AZ-SB-Install will be created + resource "azurerm_storage_blob" "CC-AZ-SB-Install" { + access_tier = (known after apply) + content_type = "application/octet-stream" + id = (known after apply) + metadata = (known after apply) + name = "InstallCWC.ps1" + parallelism = 8 + size = 0 + source = "InstallCWC.ps1" + storage_account_name = "saxxxxxxxxxxxxxx" + storage_container_name = "scsaxxxxxxxxxxxxxx" + type = "Block" + url = (known after apply) } # azurerm_storage_blob.CC-AZ-SB-InstallPreReqs-ps1 will be created + resource "azurerm_storage_blob" "CC-AZ-SB-InstallPreReqs-ps1" { + access_tier = (known after apply) + content_type = "application/octet-stream" + id = (known after apply) + metadata = (known after apply) + name = "InstallPreReqs.ps1" + parallelism = 8 + size = 0 + source = "InstallPreReqs.ps1" + storage_account_name = "saxxxxxxxxxxxxxx" + storage_container_name = "scsaxxxxxxxxxxxxxx" + type = "Block" + url = (known after apply) } # azurerm_storage_blob.CC-AZ-SB-JSON will be created + resource "azurerm_storage_blob" "CC-AZ-SB-JSON" { + access_tier = (known after apply) + content_type = "application/octet-stream" + id = (known after apply) + metadata = (known after apply) + name = "cwc.json" + parallelism = 8 + size = 0 + source = "cwc.json" + storage_account_name = "saxxxxxxxxxxxxxx" + storage_container_name = "scsaxxxxxxxxxxxxxx" + type = "Block" + url = (known after apply) } # azurerm_subnet.CC-AZ-TF will be created + resource "azurerm_subnet" "CC-AZ-TF" { + address_prefixes = [ + "10.0.0.0/24", ] + enforce_private_link_endpoint_network_policies = false + enforce_private_link_service_network_policies = false + id = (known after apply) + name = "SN-TF-AZ" + resource_group_name = "RG-CC-AZ-TF-TEST" + virtual_network_name = "VNET-TF-AZ-CC1" } # azurerm_subnet_network_security_group_association.CC-AZ-TF will be created + resource "azurerm_subnet_network_security_group_association" "CC-AZ-TF" { + id = (known after apply) + network_security_group_id = (known after apply) + subnet_id = (known after apply) } # azurerm_virtual_machine_extension.DownloadPSScriptToLocalVMAndExecuteItOnCC1 will be created + resource "azurerm_virtual_machine_extension" "DownloadPSScriptToLocalVMAndExecuteItOnCC1" { + id = (known after apply) + name = "DownloadPSScriptToLocalVMAndExecuteIt" + publisher = "Microsoft.Compute" + settings = <<-EOT { "commandToExecute": "powershell.exe -ExecutionPolicy Unrestricted -Command \"Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force; Install-Module -Name PowerShellGet -Force; Invoke-WebRequest -Uri https://saxxxxxxxxxxxxxx.blob.core.windows.net/scsaxxxxxxxxxxxxxx/download.ps1 -OutFile c:/temp/download.ps1; c:/temp/download.ps1; c:/temp/InstallPreReqs.ps1\"" } EOT + type = "CustomScriptExtension" + type_handler_version = "1.9" + virtual_machine_id = (known after apply) } # azurerm_virtual_machine_extension.DownloadPSScriptToLocalVMAndExecuteItOnCC2 will be created + resource "azurerm_virtual_machine_extension" "DownloadPSScriptToLocalVMAndExecuteItOnCC2" { + id = (known after apply) + name = "DownloadPSScriptToLocalVMAndExecuteIt" + publisher = "Microsoft.Compute" + settings = <<-EOT { "commandToExecute": "powershell.exe -ExecutionPolicy Unrestricted -Command \"Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force; Install-Module -Name PowerShellGet -Force; Invoke-WebRequest -Uri https://saxxxxxxxxxxxxxx.blob.core.windows.net/scsaxxxxxxxxxxxxxx/download.ps1 -OutFile c:/temp/download.ps1; c:/temp/download.ps1; c:/temp/InstallPreReqs.ps1\"" } EOT + type = "CustomScriptExtension" + type_handler_version = "1.9" + virtual_machine_id = (known after apply) } # azurerm_virtual_machine_extension.create-active-directory-forest will be created + resource "azurerm_virtual_machine_extension" "create-active-directory-forest" { + id = (known after apply) + name = "create-active-directory-forest" + publisher = "Microsoft.Compute" + settings = (sensitive value) + type = "CustomScriptExtension" + type_handler_version = "1.9" + virtual_machine_id = (known after apply) } # azurerm_virtual_network.CC-AZ-TF will be created + resource "azurerm_virtual_network" "CC-AZ-TF" { + address_space = [ + "10.0.0.0/16", ] + dns_servers = (known after apply) + guid = (known after apply) + id = (known after apply) + location = "eastus" + name = "VNET-TF-AZ-CC1" + resource_group_name = "RG-CC-AZ-TF-TEST" + subnet = (known after apply) } # azurerm_virtual_network_dns_servers.CC-AZ-TF will be created + resource "azurerm_virtual_network_dns_servers" "CC-AZ-TF" { + dns_servers = [ + "10.0.0.4", ] + id = (known after apply) + virtual_network_id = (known after apply) } # azurerm_windows_virtual_machine.CC-AZ-TF will be created + resource "azurerm_windows_virtual_machine" "CC-AZ-TF" { + admin_password = (sensitive value) + admin_username = "azxxxxxx" + allow_extension_operations = true + computer_name = (known after apply) + enable_automatic_updates = true + extensions_time_budget = "PT1H30M" + hotpatching_enabled = false + id = (known after apply) + location = "eastus" + max_bid_price = -1 + name = "VM-TF-CCCC1" + network_interface_ids = (known after apply) + patch_mode = "AutomaticByOS" + platform_fault_domain = -1 + priority = "Regular" + private_ip_address = (known after apply) + private_ip_addresses = (known after apply) + provision_vm_agent = true + public_ip_address = (known after apply) + public_ip_addresses = (known after apply) + resource_group_name = "RG-CC-AZ-TF-TEST" + size = "Standard_D2s_v5" + virtual_machine_id = (known after apply) + os_disk { + caching = "ReadWrite" + disk_size_gb = (known after apply) + name = "OsDisk1" + storage_account_type = "Standard_LRS" + write_accelerator_enabled = false } + source_image_reference { + offer = "WindowsServer" + publisher = "MicrosoftWindowsServer" + sku = "2022-datacenter-azure-edition" + version = "latest" } } # azurerm_windows_virtual_machine.CC-AZ-TF-DC will be created + resource "azurerm_windows_virtual_machine" "CC-AZ-TF-DC" { + admin_password = (sensitive value) + admin_username = "azxxxxxx" + allow_extension_operations = true + computer_name = (known after apply) + enable_automatic_updates = true + extensions_time_budget = "PT1H30M" + hotpatching_enabled = false + id = (known after apply) + location = "eastus" + max_bid_price = -1 + name = "VM-TF-CC-DC" + network_interface_ids = (known after apply) + patch_mode = "AutomaticByOS" + platform_fault_domain = -1 + priority = "Regular" + private_ip_address = (known after apply) + private_ip_addresses = (known after apply) + provision_vm_agent = true + public_ip_address = (known after apply) + public_ip_addresses = (known after apply) + resource_group_name = "RG-CC-AZ-TF-TEST" + size = "Standard_D2s_v5" + virtual_machine_id = (known after apply) + os_disk { + caching = "ReadWrite" + disk_size_gb = (known after apply) + name = "OsDisk-DC" + storage_account_type = "Standard_LRS" + write_accelerator_enabled = false } + source_image_reference { + offer = "WindowsServer" + publisher = "MicrosoftWindowsServer" + sku = "2022-datacenter-azure-edition" + version = "latest" } } # azurerm_windows_virtual_machine.CC-AZ-TF2 will be created + resource "azurerm_windows_virtual_machine" "CC-AZ-TF2" { + admin_password = (sensitive value) + admin_username = "azxxxxxx" + allow_extension_operations = true + computer_name = (known after apply) + enable_automatic_updates = true + extensions_time_budget = "PT1H30M" + hotpatching_enabled = false + id = (known after apply) + location = "eastus" + max_bid_price = -1 + name = "VM-TF-CCCC2" + network_interface_ids = (known after apply) + patch_mode = "AutomaticByOS" + platform_fault_domain = -1 + priority = "Regular" + private_ip_address = (known after apply) + private_ip_addresses = (known after apply) + provision_vm_agent = true + public_ip_address = (known after apply) + public_ip_addresses = (known after apply) + resource_group_name = "RG-CC-AZ-TF-TEST" + size = "Standard_D2s_v5" + virtual_machine_id = (known after apply) + os_disk { + caching = "ReadWrite" + disk_size_gb = (known after apply) + name = "OsDisk2" + storage_account_type = "Standard_LRS" + write_accelerator_enabled = false } + source_image_reference { + offer = "WindowsServer" + publisher = "MicrosoftWindowsServer" + sku = "2022-datacenter-azure-edition" + version = "latest" } } # 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 = "./cwc.json" + id = (known after apply) } # local_file.Download-ps1 will be created + resource "local_file" "Download-ps1" { + content = <<-EOT Install-Module -Name Az -Repository PSGallery -Force Start-Sleep -Seconds 180 $cont = New-AzStorageContext -StorageAccountName saxxxxxxxxxxxxxx -Anonymous -Protocol https Get-AzStorageBlobContent -blob cwcconnector.exe -container scsaxxxxxxxxxxxxxx -Context $cont -destination c:/temp Get-AzStorageBlobContent -blob cwc.json -container scsaxxxxxxxxxxxxxx -Context $cont -destination c:/temp Get-AzStorageBlobContent -blob InstallPreReqs.ps1 -container scsaxxxxxxxxxxxxxx -Context $cont -destination c:/temp Get-AzStorageBlobContent -blob InstallCWC.ps1 -container scsaxxxxxxxxxxxxxx -Context $cont -destination c:/temp Get-AzStorageBlobContent -blob CitrixPoshSdk.exe -container scsaxxxxxxxxxxxxxx -Context $cont -destination c:/temp EOT + 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 = "./download.ps1" + id = (known after apply) } # local_file.InstallCWC-ps1 will be created + resource "local_file" "InstallCWC-ps1" { + content = <<-EOT $CtxPoSHSDK = Get-WmiObject -Class Win32_Product | Where-Object{$_.Name -Like "Citrix*"} if ($CtxPoSHSDK -ne $null) { $CtxPoSHSDK.Uninstall() } cd /temp ./CWCConnector.exe /q /ParametersFilePath:c:/temp/cwc.json EOT + 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 = "./InstallCWC.ps1" + id = (known after apply) } # local_file.InstallPreReqs-ps1 will be created + resource "local_file" "InstallPreReqs-ps1" { + 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 = "./InstallPreReqs.ps1" + id = (known after apply) } # random_uuid.IDforCCRL will be created + resource "random_uuid" "IDforCCRL" { + id = (known after apply) + result = (known after apply) } # restapi_object.CreateRL will be created + resource "restapi_object" "CreateRL" { + api_data = (known after apply) + api_response = (known after apply) + create_response = (known after apply) + data = (known after apply) + id = (known after apply) + path = "/resourcelocations" } # terraform_data.Download-CWC-Installer will be created + resource "terraform_data" "Download-CWC-Installer" { + id = (known after apply) } # time_sleep.wait_1_minutes will be created + resource "time_sleep" "wait_1_minutes" { + create_duration = "60s" + id = (known after apply) } # time_sleep.wait_2_minutes_after_adcompleted will be created + resource "time_sleep" "wait_2_minutes_after_adcompleted" { + create_duration = "120s" + id = (known after apply) } # time_sleep.wait_3_minutes will be created + resource "time_sleep" "wait_3_minutes" { + create_duration = "180s" + id = (known after apply) } # time_sleep.wait_4_minutes will be created + resource "time_sleep" "wait_4_minutes" { + create_duration = "240s" + id = (known after apply) } # time_sleep.wait_5_minutes will be created + resource "time_sleep" "wait_5_minutes" { + create_duration = "300s" + id = (known after apply) } Plan: 34 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 terraform_data.Download-CWC-Installer: Creating... terraform_data.Download-CWC-Installer: Provisioning with 'local-exec'... random_uuid.IDforCCRL: Creating... terraform_data.Download-CWC-Installer (local-exec): (output suppressed due to sensitive value in config) time_sleep.wait_3_minutes: Creating... time_sleep.wait_4_minutes: Creating... random_uuid.IDforCCRL: Creation complete after 0s [id=00adb956-95b6-d02b-ac59-dc0ea0be376f] time_sleep.wait_5_minutes: Creating... time_sleep.wait_1_minutes: Creating... local_file.InstallPreReqs-ps1: Creating... local_file.Download-ps1: Creating... local_file.InstallCWC-ps1: Creating... local_file.InstallPreReqs-ps1: Creation complete after 0s [id=899f6d4582959d5a83dfce71e589207e4030aae6] local_file.InstallCWC-ps1: Creation complete after 0s [id=a6adfaeb00b7a0bb9d5951ba144b4e16b62b8ce9] local_file.Download-ps1: Creation complete after 0s [id=0f2bd28b7dd7c10e07001e8f56d9811bf0d36430] restapi_object.CreateRL: Creating... restapi_object.CreateRL: Creation complete after 0s [id=00adb956-95b6-d02b-ac59-dc0ea0be376f] local_file.CWC-Configuration: Creating... local_file.CWC-Configuration: Creation complete after 0s [id=0257d2b92f197fa4d043b6cd4c4959be284dddc2] terraform_data.Download-CWC-Installer: Creation complete after 2s [id=0c240015-d833-e198-88ac-49ec756319bf] azurerm_public_ip.CC-AZ-TF-DC: Creating... azurerm_public_ip.CC-AZ-TF2: Creating... azurerm_public_ip.CC-AZ-TF: Creating... azurerm_managed_disk.CreateManagedDiskFromUploadedVHD: Creating... azurerm_virtual_network.CC-AZ-TF: Creating... azurerm_network_security_group.CC-AZ-TF: Creating... azurerm_network_security_group.CC-AZ-TF: Creation complete after 3s [id=/subscriptions/58xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/RG-CC-AZ-TF-TEST/providers/Microsoft.Network/networkSecurityGroups/NSG -TF-AZ-CC] azurerm_public_ip.CC-AZ-TF2: Creation complete after 3s [id=/subscriptions/58xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/RG-CC-AZ-TF-TEST/providers/Microsoft.Network/publicIPAddresses/PUBIP-TF-AZ-CC2] azurerm_public_ip.CC-AZ-TF-DC: Creation complete after 3s [id=/subscriptions/58xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/RG-CC-AZ-TF-TEST/providers/Microsoft.Network/publicIPAddresses/PUBIP-TF-AZ-CC-DC ] azurerm_public_ip.CC-AZ-TF: Creation complete after 3s [id=/subscriptions/58xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/RG-CC-AZ-TF-TEST/providers/Microsoft.Network/publicIPAddresses/PUBIP-TF-AZ-CC1] azurerm_managed_disk.CreateManagedDiskFromUploadedVHD: Creation complete after 5s [id=/subscriptions/58xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/RG-CC-AZ-TF-TEST/providers/Microsoft.Compute/disks/w11-m in.vhd] azurerm_virtual_network.CC-AZ-TF: Creation complete after 6s [id=/subscriptions/58xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/RG-CC-AZ-TF-TEST/providers/Microsoft.Network/virtualNetworks/VNET-TF-AZ-CC1] azurerm_virtual_network_dns_servers.CC-AZ-TF: Creating... azurerm_subnet.CC-AZ-TF: Creating... time_sleep.wait_5_minutes: Still creating... [10s elapsed] time_sleep.wait_3_minutes: Still creating... [10s elapsed] time_sleep.wait_4_minutes: Still creating... [10s elapsed] time_sleep.wait_1_minutes: Still creating... [10s elapsed] azurerm_virtual_network_dns_servers.CC-AZ-TF: Creation complete after 6s [id=/subscriptions/58xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/RG-CC-AZ-TF-TEST/providers/Microsoft.Network/virtualNetworks/VNET -TF-AZ-CC1/dnsServers/default] azurerm_subnet.CC-AZ-TF: Still creating... [10s elapsed] time_sleep.wait_1_minutes: Still creating... [20s elapsed] time_sleep.wait_5_minutes: Still creating... [20s elapsed] time_sleep.wait_4_minutes: Still creating... [20s elapsed] time_sleep.wait_3_minutes: Still creating... [20s elapsed] azurerm_subnet.CC-AZ-TF: Creation complete after 12s [id=/subscriptions/58xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/RG-CC-AZ-TF-TEST/providers/Microsoft.Network/virtualNetworks/VNET-TF-AZ-CC1/subnets/S N-TF-AZ] azurerm_subnet_network_security_group_association.CC-AZ-TF: Creating... azurerm_network_interface.CC-AZ-TF-DC: Creating... azurerm_subnet_network_security_group_association.CC-AZ-TF: Creation complete after 6s [id=/subscriptions/58xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/RG-CC-AZ-TF-TEST/providers/Microsoft.Network/virtua lNetworks/VNET-TF-AZ-CC1/subnets/SN-TF-AZ] azurerm_network_interface.CC-AZ-TF-DC: Creation complete after 8s [id=/subscriptions/58xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/RG-CC-AZ-TF-TEST/providers/Microsoft.Network/networkInterfaces/NIC-TF-AZ -DC] azurerm_network_interface.CC-AZ-TF2: Creating... azurerm_network_interface.CC-AZ-TF: Creating... azurerm_windows_virtual_machine.CC-AZ-TF-DC: Creating... time_sleep.wait_3_minutes: Still creating... [30s elapsed] time_sleep.wait_4_minutes: Still creating... [30s elapsed] time_sleep.wait_5_minutes: Still creating... [30s elapsed] time_sleep.wait_1_minutes: Still creating... [30s elapsed] azurerm_network_interface.CC-AZ-TF: Creation complete after 2s [id=/subscriptions/58xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/RG-CC-AZ-TF-TEST/providers/Microsoft.Network/networkInterfaces/NIC-TF-AZ-CC 1] azurerm_windows_virtual_machine.CC-AZ-TF: Creating... azurerm_network_interface.CC-AZ-TF2: Creation complete after 3s [id=/subscriptions/58xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/RG-CC-AZ-TF-TEST/providers/Microsoft.Network/networkInterfaces/NIC-TF-AZ-C C2] azurerm_windows_virtual_machine.CC-AZ-TF2: Creating... azurerm_windows_virtual_machine.CC-AZ-TF-DC: Still creating... [10s elapsed] time_sleep.wait_1_minutes: Still creating... [40s elapsed] time_sleep.wait_4_minutes: Still creating... [40s elapsed] time_sleep.wait_5_minutes: Still creating... [40s elapsed] time_sleep.wait_3_minutes: Still creating... [40s elapsed] azurerm_windows_virtual_machine.CC-AZ-TF: Still creating... [10s elapsed] azurerm_windows_virtual_machine.CC-AZ-TF2: Still creating... [10s elapsed] azurerm_windows_virtual_machine.CC-AZ-TF-DC: Still creating... [20s elapsed] time_sleep.wait_3_minutes: Still creating... [50s elapsed] time_sleep.wait_5_minutes: Still creating... [50s elapsed] time_sleep.wait_4_minutes: Still creating... [50s elapsed] time_sleep.wait_1_minutes: Still creating... [50s elapsed] azurerm_windows_virtual_machine.CC-AZ-TF: Still creating... [20s elapsed] azurerm_windows_virtual_machine.CC-AZ-TF2: Creation complete after 19s [id=/subscriptions/58xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/RG-CC-AZ-TF-TEST/providers/Microsoft.Compute/virtualMachines/VM-TF- CCCC2] azurerm_windows_virtual_machine.CC-AZ-TF-DC: Creation complete after 24s [id=/subscriptions/58xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/RG-CC-AZ-TF-TEST/providers/Microsoft.Compute/virtualMachines/VM-T F-CC-DC] azurerm_windows_virtual_machine.CC-AZ-TF: Creation complete after 23s [id=/subscriptions/58xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/RG-CC-AZ-TF-TEST/providers/Microsoft.Compute/virtualMachines/VM-TF-C CCC1] time_sleep.wait_1_minutes: Creation complete after 1m0s [id=2023-12-21T17:09:00Z] azurerm_storage_blob.CC-AZ-SB-Install: Creating... azurerm_storage_blob.CC-AZ-SB-DL: Creating... azurerm_storage_blob.CC-AZ-SB-JSON: Creating... azurerm_storage_blob.CC-AZ-SB-InstallPreReqs-ps1: Creating... time_sleep.wait_3_minutes: Still creating... [1m0s elapsed] time_sleep.wait_4_minutes: Still creating... [1m0s elapsed] time_sleep.wait_5_minutes: Still creating... [1m0s elapsed] azurerm_storage_blob.CC-AZ-SB-DL: Creation complete after 4s [id=https://saxxxxxxxxxxxxx.blob.core.windows.net/scsaxxxxxxxxxxxxx/download.ps1] azurerm_storage_blob.CC-AZ-SB-Install: Creation complete after 4s [id=https://saxxxxxxxxxxxxx.blob.core.windows.net/scsaxxxxxxxxxxxxx/InstallCWC.ps1] azurerm_storage_blob.CC-AZ-SB-InstallPreReqs-ps1: Creation complete after 4s [id=https://saxxxxxxxxxxxxx.blob.core.windows.net/scsaxxxxxxxxxxxxx/InstallPreReqs.ps1] azurerm_storage_blob.CC-AZ-SB-JSON: Creation complete after 4s [id=https://saxxxxxxxxxxxxx.blob.core.windows.net/scsaxxxxxxxxxxxxx/cwc.json] time_sleep.wait_4_minutes: Still creating... [1m10s elapsed] … … … time_sleep.wait_5_minutes: Still creating... [4m50s elapsed] time_sleep.wait_5_minutes: Creation complete after 5m0s [id=2023-12-21T17:13:00Z] azurerm_virtual_machine_extension.create-active-directory-forest: Creating... azurerm_virtual_machine_extension.create-active-directory-forest: Still creating... [10s elapsed] azurerm_virtual_machine_extension.create-active-directory-forest: Still creating... [20s elapsed] … … … azurerm_virtual_machine_extension.create-active-directory-forest: Still creating... [4m30s elapsed] azurerm_virtual_machine_extension.create-active-directory-forest: Creation complete after 4m36s [id=/subscriptions/58xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/RG-CC-AZ-TF-TEST/providers/Microsoft.Compu te/virtualMachines/VM-TF-CC-DC/extensions/create-active-directory-forest] time_sleep.wait_2_minutes_after_adcompleted: Creating... time_sleep.wait_2_minutes_after_adcompleted: Still creating... [10s elapsed] … … … time_sleep.wait_2_minutes_after_adcompleted: Still creating... [1m51s elapsed] time_sleep.wait_2_minutes_after_adcompleted: Creation complete after 2m0s [id=2023-12-21T17:19:37Z] azurerm_virtual_machine_extension.DownloadPSScriptToLocalVMAndExecuteItOnCC1: Creating... azurerm_virtual_machine_extension.DownloadPSScriptToLocalVMAndExecuteItOnCC2: Creating... azurerm_virtual_machine_extension.DownloadPSScriptToLocalVMAndExecuteItOnCC1: Still creating... [10s elapsed] azurerm_virtual_machine_extension.DownloadPSScriptToLocalVMAndExecuteItOnCC2: Still creating... [10s elapsed] … … … azurerm_virtual_machine_extension.DownloadPSScriptToLocalVMAndExecuteItOnCC1: Still creating... [11m11s elapsed] azurerm_virtual_machine_extension.DownloadPSScriptToLocalVMAndExecuteItOnCC2: Creation complete after 11m13s [id=/subscriptions/58xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/RG-CC-AZ-TF-TEST/providers/Microsoft.Compute/virtualMachines/VM-TF-CCCC2/extensions/DownloadPSScriptToLocalVMAndExecuteIt] azurerm_virtual_machine_extension.DownloadPSScriptToLocalVMAndExecuteItOnCC1: Creation complete after 11m14s [id=/subscriptions/58xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/RG-CC-AZ-TF-TEST/providers/Microsoft.Compute/virtualMachines/VM-TF-CCCC1/extensions/DownloadPSScriptToLocalVMAndExecuteIt] Apply complete! Resources: 34 added, 0 changed, 0 destroyed. PS C:\_TACG\__CConAzureCreation>
This configuration completes the creation of all initial resources:
- One VM, used as a dedicated Domain Controller (DC)
- Two VMs, used as Cloud Controllers (CC)
- Creating a forest on the DC
- Putting the 2 CCs into the new domain
- Installing the needed software on the CCs
- Creating a Resource Location in Citrix Cloud
- Configuring the 2 CCs as Cloud Controllers
- Registering the 2 CCs in the newly created Resource Location
As the current Terraform providers are partially not able to do all needed configurations like joining a VM to the domain, installing, and removing software components, the providers start PowerShell scripts to complete the configuration.
Example:
After successful creation of the VMs, Terraform puts this PowerShell script in action.
The script downloads the next software components and joins the CC-VMs to the domain and sets the next PowerShell scripts to be started after the necessary reboot.
Start-Sleep -Seconds 60 Invoke-WebRequest -Uri https://${var.azurerm-storage_account_name}.blob.core.windows.net/${var.azurerm-storage_account_container_name}/cwcconnector.exe -OutFile c:/temp/cwcconnector.exe Invoke-WebRequest -Uri https://${var.azurerm-storage_account_name}.blob.core.windows.net/${var.azurerm-storage_account_container_name}/cwc.json -OutFile c:/temp/cwc.json Invoke-WebRequest -Uri https://${var.azurerm-storage_account_name}.blob.core.windows.net/${var.azurerm-storage_account_container_name}/InstallPreReqs.ps1 -OutFile c:/temp/InstallPreReqs.ps1 Invoke-WebRequest -Uri https://${var.azurerm-storage_account_name}.blob.core.windows.net/${var.azurerm-storage_account_container_name}/InstallCWC.ps1 -OutFile c:/temp/InstallCWC.ps1 Invoke-WebRequest -Uri https://${var.azurerm-storage_account_name}.blob.core.windows.net/${var.azurerm-storage_account_container_name}/CitrixPoshSdk.exe -OutFile c:/temp/CitrixPoshSdk.exe Set-ItemProperty -Path "HKLM:\Software\Microsoft\Windows\CurrentVersion\RunOnce" -Name "install" -Value "powershell.exe -ExecutionPolicy Unrestricted -file c:/temp/InstallPreReqs.ps1" Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" -Name "AutoAdminLogon" -Value "1" Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" -Name "DefaultUsername" -Value "xxxxxxxxxxxx" Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" -Name "DefaultPassword" -Value "xxxxxxxxxxxx" Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" -Name "DefaultDomain" -Value "tf.the-austrian-citrix-guy.at" Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" -Name "AutoLogonCount" -Value "1" netdom.exe join $env:COMPUTERNAME /domain:tf.the-austrian-citrix-guy.at /UserD:XXXXXXXXXXXXXXXXXXXX /PasswordD:XXXXXXXXXXXXXXXXXXXXXXX /reboot:5
After successful runs of all needed scripts we can see the new Resource Group in Azure, the 3 VMs in Azure, the new Resource Location in Citrix Cloud and the 2 Cloud Controllers bound to the Resource Location:
In Citrix Cloud the new Resource Location with the Cloud Connectors is visible:
The environment is now ready to deploy a Machine Catalog and a Delivery Group using Module 3.
Module 3: Create all Resources in Microsoft Azure and Citrix Cloud
This module is split into the following configuration parts:
- Retrieving the Site- and Zone-ID of the new Resource Location
- Creating a Hypervisor Connection to Azure and a Hypervisor 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
The Terraform configuration contains some idle time slots to make sure that background operations on Azure or on the VMs can completed before the next configuration steps occur.
We have seen different elapsed configuration times related to different loads on the Azure systems!
Before Terraform can created the Hypervisor Connection and the Hypervisor Pool, Terraform needs to 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 runs PowerShell scripts to retrieve the IDs.
It creates the necessary scripts with all needed variables, saves the scripts and executes them.
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 Azure and a Hypervisor Resource Pool associated with the Hypervisor Connection. As soon as these prerequisites are completed, the Machine Catalog is created. After the successful creation of the Hypervisor Connection, the Hypervisor Resource Pool and the Machine Catalog the last step of the deployment process starts - the creation of the Delivery Group.
NOTE: In the example below we do not use the uploaded Master Image, we use an Azure-based Windows 11-Enterprise-MultiSession VM as Master. To use the uploaded image, you only need to change the corresponding variable
azurerm-masterimage-name
in theCConAzCitrixCloudStuff-main.auto.tfvars.json
file. Be sure to change the variablescc-mc-allocationtype
andcc-mc-sessiontype
in theCConAzCitrixCloudStuff-main.auto.tfvars.json
file accordingly - depending on the VDA type.
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.
Please make sure you have configured the variables according to your needs.
NOTE: Each entity can be solely configured using the corresponding
.auto.tfvars.json
file. The Hypervisor-related entities can be configured using the correspondingCConAzCitrixCloudStuff-hyp.auto.tfvars.json
file. The Machine Catalog entity can be configured using the correspondingCConAzCitrixCloudStuff-mc.auto.tfvars.json
file. The Delivery Group entity can be configured using the correspondingCConAzCitrixCloudStuff-dg.auto.tfvars.json
file.
Caution:
Be sure that your subscription has no quota limitation on the chosen VM type and you have enough resources on Azure 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!
Example:
Checking the vCPU usage and limit for a D2S_v5
VM which we plan to use in this guide:
$Location = 'East US' $VMSize = 'Standard_D2s_v5' $SKU = Get-AzComputeResourceSku -Location $Location | where ResourceType -eq "virtualMachines" | select Name,Family $VMFamily = ($SKU | where Name -eq $VMSize | select -Property Family).Family Get-AzVMUsage -Location $Location | Where-Object { $_.Name.Value -eq $VMFamily }
PS C:\TACG> $Location = 'East US' PS C:\TACG> $VMSize = 'Standard_D2s_v5' PS C:\TACG> $SKU = Get-AzComputeResourceSku -Location $Location | where ResourceType -eq "virtualMachines" | select Name,Family PS C:\TACG> $VMFamily = ($SKU | where Name -eq $VMSize | select -Property Family).Family PS C:\TACG> Get-AzVMUsage -Location $Location | Where-Object { $_.Name.Value -eq $VMFamily } Name Current Value Limit Unit ---- ------------- ----- ---- Standard DSv5 Family vCPUs 10 16 Count PS C:\TACG>
This example shows that we can use a maximum of 3 further D2S_v5
VMs before exceeding the limit.
The vCPU quota or any other quota can be increased using the Azure Console or PowerShell.
More information about increasing vCPU quotas can be found here: https://learn.microsoft.com/en-us/azure/quotas/per-vm-quota-requests
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\> terraform plan 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: # data.local_file.input_site will be read during apply # (depends on a resource or a module with changes pending) <= data "local_file" "input_site" { + content = (known after apply) + content_base64 = (known after apply) + 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) + filename = "./GetSiteID.txt" + id = (known after apply) } # data.local_file.input_zone will be read during apply # (depends on a resource or a module with changes pending) <= data "local_file" "input_zone" { + content = (known after apply) + content_base64 = (known after apply) + 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) + filename = "./GetZoneID.txt" + 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 = "./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 = "./GetZoneID.ps1" + 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) } # citrix_daas_delivery_group.TF-CC-DG will be created + resource "citrix_daas_delivery_group" "TF-CC-DG" { + associated_machine_catalogs = [ + { + machine_catalog = (known after apply) + machine_count = 1 }, ] + autoscale_enabled = true + autoscale_settings = { + disconnect_off_peak_idle_session_after_seconds = 120 + disconnect_peak_idle_session_after_seconds = 120 + log_off_off_peak_disconnected_session_after_seconds = 120 + log_off_peak_disconnected_session_after_seconds = 120 + off_peak_buffer_size_percent = 1 + peak_buffer_size_percent = 1 + power_time_schemes = [ + { + days_of_week = [ + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", ] + display_name = "TF Schedule" + peak_time_ranges = [ + "09:00-17:00", ] + pool_size_schedules = [ + { + pool_size = 0 + time_range = "00:00-09:00" }, + { + pool_size = 1 + time_range = "09:00-17:00" }, + { + pool_size = 0 + time_range = "17:00-00:00" }, ] + pool_using_percentage = false }, ] } + description = "TF-created Delivery Group" + id = (known after apply) + name = "TF-CC-DG-MCAZN" + total_machines = (known after apply) + users = [ + "xxxxxxxxxx@tf.the-austrian-citrix-guy.at", + "xxxxxxxxxx@tf.the-austrian-citrix-guy.at", ] } # citrix_daas_hypervisor.TF-CC-HYPV will be created + resource "citrix_daas_hypervisor" "TF-CC-HYPV" { + active_directory_id = (sensitive value) + application_id = (sensitive value) + application_secret = (sensitive value) + connection_type = "AzureRM" + id = (known after apply) + name = "TF-CC-HYP-AZ" + subscription_id = (sensitive value) + zone = "28xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" } # citrix_daas_hypervisor_resource_pool.TF-CC-HYPV-POOL will be created + resource "citrix_daas_hypervisor_resource_pool" "TF-CC-HYPV-POOL" { + hypervisor = (known after apply) + hypervisor_connection_type = (known after apply) + id = (known after apply) + name = "TF-CC-HYP-RP-AZ" + region = "eastus" + subnets = [ + "SN-TF-AZ", ] + virtual_network = "VNET-TF-AZ-CC1" + virtual_network_resource_group = "RG-CC-AZ-TF-TEST" } # citrix_daas_machine_catalog.TF-CC-MC will be created + resource "citrix_daas_machine_catalog" "TF-CC-MC" { + allocation_type = "Random" + description = "AzureRM" + id = (known after apply) + name = "TF-CC-MC-MCAZ-MS" + provisioning_scheme = { + machine_account_creation_rules = { + domain = "tf.the-austrian-citrix-guy.at" + naming_scheme = "TF-VM-W11N-#" + naming_scheme_type = "Numeric" } + machine_config = { + hypervisor = (known after apply) + hypervisor_resource_pool = (known after apply) + master_image = "VM-TF-W11-ENT-MIM_OsDisk_1_xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + resource_group = "RG-CC-AZ-TF-TEST" + service_offering = "Standard_D2s_v5" } + network_mapping = { + network = "SN-TF-AZ" + network_device = "0" } + number_of_total_machines = 1 + storage_type = "Standard_LRS" + use_managed_disks = true } + service_account = "xxxxxxxxxx" + service_account_password = (sensitive value) + session_support = "MultiSession" + vda_upgrade_type = (known after apply) + zone = "28xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" } # terraform_data.ZoneID will be created + resource "terraform_data" "ZoneID" { + id = (known after apply) } # time_sleep.wait_2_minutes_dg will be created + resource "time_sleep" "wait_2_minutes_dg" { + create_duration = "120s" + id = (known after apply) } Plan: 12 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> terraform apply 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: # data.local_file.input_site will be read during apply # (depends on a resource or a module with changes pending) <= data "local_file" "input_site" { + content = (known after apply) + content_base64 = (known after apply) + 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) + filename = "./GetSiteID.txt" + id = (known after apply) } # data.local_file.input_zone will be read during apply # (depends on a resource or a module with changes pending) <= data "local_file" "input_zone" { + content = (known after apply) + content_base64 = (known after apply) + 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) + filename = "./GetZoneID.txt" + 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 = "./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 = "./GetZoneID.ps1" + 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) } # citrix_daas_delivery_group.TF-CC-DG will be created + resource "citrix_daas_delivery_group" "TF-CC-DG" { + associated_machine_catalogs = [ + { + machine_catalog = (known after apply) + machine_count = 1 }, ] + autoscale_enabled = true + autoscale_settings = { + disconnect_off_peak_idle_session_after_seconds = 120 + disconnect_peak_idle_session_after_seconds = 120 + log_off_off_peak_disconnected_session_after_seconds = 120 + log_off_peak_disconnected_session_after_seconds = 120 + off_peak_buffer_size_percent = 1 + peak_buffer_size_percent = 1 + power_time_schemes = [ + { + days_of_week = [ + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", ] + display_name = "TF Schedule" + peak_time_ranges = [ + "09:00-17:00", ] + pool_size_schedules = [ + { + pool_size = 0 + time_range = "00:00-09:00" }, + { + pool_size = 1 + time_range = "09:00-17:00" }, + { + pool_size = 0 + time_range = "17:00-00:00" }, ] + pool_using_percentage = false }, ] } + description = "TF-created Delivery Group" + id = (known after apply) + name = "TF-CC-DG-MCAZN" + total_machines = (known after apply) + users = [ + "xxxxxxxxxx@tf.the-austrian-citrix-guy.at", + "xxxxxxxxxx@tf.the-austrian-citrix-guy.at", ] } # citrix_daas_hypervisor.TF-CC-HYPV will be created + resource "citrix_daas_hypervisor" "TF-CC-HYPV" { + active_directory_id = (sensitive value) + application_id = (sensitive value) + application_secret = (sensitive value) + connection_type = "AzureRM" + id = (known after apply) + name = "TF-CC-HYP-AZ" + subscription_id = (sensitive value) + zone = "28d7d635-76f1-4604-91d4-68c4af3a88f8" } # citrix_daas_hypervisor_resource_pool.TF-CC-HYPV-POOL will be created + resource "citrix_daas_hypervisor_resource_pool" "TF-CC-HYPV-POOL" { + hypervisor = (known after apply) + hypervisor_connection_type = (known after apply) + id = (known after apply) + name = "TF-CC-HYP-RP-AZ" + region = "eastus" + subnets = [ + "SN-TF-AZ", ] + virtual_network = "VNET-TF-AZ-CC1" + virtual_network_resource_group = "RG-CC-AZ-TF-TEST" } # citrix_daas_machine_catalog.TF-CC-MC will be created + resource "citrix_daas_machine_catalog" "TF-CC-MC" { + allocation_type = "Random" + description = "AzureRM" + id = (known after apply) + name = "TF-CC-MC-MCAZ-MS" + provisioning_scheme = { + machine_account_creation_rules = { + domain = "tf.the-austrian-citrix-guy.at" + naming_scheme = "TF-VM-W11N-#" + naming_scheme_type = "Numeric" } + machine_config = { + hypervisor = (known after apply) + hypervisor_resource_pool = (known after apply) + master_image = "VM-TF-W11-ENT-MIM_OsDisk_1_ea4c5c324f2f477bad3beca64c9489d2" + resource_group = "RG-CC-AZ-TF-TEST" + service_offering = "Standard_D2s_v5" } + network_mapping = { + network = "SN-TF-AZ" + network_device = "0" } + number_of_total_machines = 1 + storage_type = "Standard_LRS" + use_managed_disks = true } + service_account = "xxxxxxxxxx" + service_account_password = (sensitive value) + session_support = "MultiSession" + vda_upgrade_type = (known after apply) + zone = "28d7d635-76f1-4604-91d4-68c4af3a88f8" } # terraform_data.ZoneID will be created + resource "terraform_data" "ZoneID" { + id = (known after apply) } # time_sleep.wait_2_minutes_dg will be created + resource "time_sleep" "wait_2_minutes_dg" { + create_duration = "120s" + id = (known after apply) } Plan: 12 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 local_file.GetSiteIDScript: Creating... local_file.GetSiteIDScript: Creation complete after 0s [id=6cfaa0a889a04fa7a821366ad524dde703988ec6] terraform_data.SiteID: Creating... terraform_data.SiteID: Provisioning with 'local-exec'... terraform_data.SiteID (local-exec): Executing: ["PowerShell" "-File" "GetSiteID.ps1"] terraform_data.SiteID: Creation complete after 1s [id=311c1a04-044e-3ee6-68b1-a4c41ecba258] data.local_file.input_site: Reading... data.local_file.input_site: Read complete after 0s [id=f684114a2b93cc095c4ac5f81999ee1a111d53b9] local_file.GetZoneIDScript: Creating... local_file.GetZoneIDScript: Creation complete after 0s [id=022a8055f784d20aa2964d1a325e496cde3a9258] terraform_data.ZoneID: Creating... terraform_data.ZoneID: Provisioning with 'local-exec'... terraform_data.ZoneID (local-exec): Executing: ["PowerShell" "-File" "GetZoneID.ps1"] terraform_data.ZoneID: Creation complete after 1s [id=ea9e229f-4f57-8c6b-b263-24af5126f50c] data.local_file.input_zone: Reading... data.local_file.input_zone: Read complete after 0s [id=f2acfc022e0892c48bf3f3e258f011b9c96a4617] time_sleep.wait_2_minutes_dg: Creating... citrix_daas_hypervisor.TF-CC-HYPV: Creating... time_sleep.wait_2_minutes_dg: Still creating... [10s elapsed] citrix_daas_hypervisor.TF-CC-HYPV: Still creating... [10s elapsed] time_sleep.wait_2_minutes_dg: Still creating... [20s elapsed] citrix_daas_hypervisor.TF-CC-HYPV: Still creating... [20s elapsed] citrix_daas_hypervisor.TF-CC-HYPV: Creation complete after 21s [id=0b74bf1e-a77c-4e7e-8910-732e344b26e9] citrix_daas_hypervisor_resource_pool.TF-CC-HYPV-POOL: Creating... citrix_daas_hypervisor_resource_pool.TF-CC-HYPV-POOL: Creation complete after 5s [id=f38e9dfc-073e-45ef-b22d-f00fee9c1f3d] citrix_daas_machine_catalog.TF-CC-MC: Creating... time_sleep.wait_2_minutes_dg: Still creating... [30s elapsed] citrix_daas_machine_catalog.TF-CC-MC: Still creating... [10s elapsed] time_sleep.wait_2_minutes_dg: Still creating... [40s elapsed] citrix_daas_machine_catalog.TF-CC-MC: Still creating... [20s elapsed] time_sleep.wait_2_minutes_dg: Still creating... [50s elapsed] citrix_daas_machine_catalog.TF-CC-MC: Still creating... [30s elapsed] time_sleep.wait_2_minutes_dg: Still creating... [1m0s elapsed] citrix_daas_machine_catalog.TF-CC-MC: Still creating... [40s elapsed] time_sleep.wait_2_minutes_dg: Still creating... [1m10s elapsed] citrix_daas_machine_catalog.TF-CC-MC: Still creating... [50s elapsed] time_sleep.wait_2_minutes_dg: Still creating... [1m20s elapsed] citrix_daas_machine_catalog.TF-CC-MC: Still creating... [1m0s elapsed] time_sleep.wait_2_minutes_dg: Still creating... [1m30s elapsed] citrix_daas_machine_catalog.TF-CC-MC: Still creating... [1m10s elapsed] time_sleep.wait_2_minutes_dg: Still creating... [1m40s elapsed] citrix_daas_machine_catalog.TF-CC-MC: Still creating... [1m20s elapsed] time_sleep.wait_2_minutes_dg: Still creating... [1m50s elapsed] citrix_daas_machine_catalog.TF-CC-MC: Still creating... [1m30s elapsed] time_sleep.wait_2_minutes_dg: Creation complete after 2m0s [id=2024-01-05T10:33:36Z] citrix_daas_machine_catalog.TF-CC-MC: Still creating... [1m40s elapsed] citrix_daas_machine_catalog.TF-CC-MC: Still creating... [1m50s elapsed] ... citrix_daas_machine_catalog.TF-CC-MC: Still creating... [7m30s elapsed] citrix_daas_machine_catalog.TF-CC-MC: Creation complete after 7m34s [id=742a3752-def3-44a3-9277-9b9c8ea8df82] citrix_daas_delivery_group.TF-CC-DG: Creating... citrix_daas_delivery_group.TF-CC-DG: Creation complete after 5s [id=30ffd8b8-59e7-4a00-b5c9-820a1ffbd58b] Apply complete! Resources: 6 added, 0 changed, 0 destroyed.
This configuration completes the full deployment of a Citrix Cloud Resource Location in Microsoft Azure.
The environment created by Terraform is now ready for usage, all entities are in place:
The Resource Location:
The Hypervisor Connection and the Hypervisor Pool:
The Machine Catalog:
The Delivery Group:
The Azure Resource Group with all Entities:
Appendix
Examples of the Terraform scripts
Module 1: CConAzureUpload
This is the Terraform configuration file for Module 1
# Configure the Providers provider "azurerm" { features {} client_id = "${var.azurerm-clientid}" client_secret = "${var.azurerm-clientsecret}" tenant_id = "${var.azurerm-tenantid}" subscription_id = "${var.azurerm-subscriptionid}" } ## Create the Resource Group on Azure resource "azurerm_resource_group" "CC-AZ-TF" { name = "${var.azurerm-resource_group_name}" location = "${var.azurerm-resource_group_location}" } ## Create the Storage Account on Azure resource "azurerm_storage_account" "CC-AZ-SB" { depends_on = [azurerm_resource_group.CC-AZ-TF,time_sleep.wait_3_minutes] name = "sat${var.cc-customerid}" resource_group_name = "${var.azurerm-resource_group_name}" location = "${var.azurerm-resource_group_location}" account_tier = "${var.azurerm-storageblob_tier}" account_replication_type = "${var.azurerm-storageblob_replication}" } ## Create the Storage Container on Azure resource "azurerm_storage_container" "CC-AZ-SB" { name = "scsat${var.cc-customerid}" storage_account_name = azurerm_storage_account.CC-AZ-SB.name container_access_type = "blob" } ## Create object for 3min waiting time - needed for Storage Blob to complete its configuration resource "time_sleep" "wait_3_minutes" { create_duration = "180s" } ## Create object for 5min waiting time - needed for all to complete before exporting the variables resource "time_sleep" "wait_5_minutes" { create_duration = "300s" } ## Create upload for CloudConnector software to Storage Blob resource "azurerm_storage_blob" "CC-AZ-SB-CWC" { depends_on = [time_sleep.wait_3_minutes] name = "cwcconnector.exe" storage_account_name = azurerm_storage_account.CC-AZ-SB.name storage_container_name = azurerm_storage_container.CC-AZ-SB.name type = "Block" source = "cwcconnector.exe" } ## Create upload for Citrix Remote PoSH SDK software to Storage Blob resource "azurerm_storage_blob" "CC-AZ-SB-CTXPOSHSDK" { depends_on = [time_sleep.wait_3_minutes] name = "CitrixPoshSdk.exe" storage_account_name = azurerm_storage_account.CC-AZ-SB.name storage_container_name = azurerm_storage_container.CC-AZ-SB.name type = "Block" source = "CitrixPoshSdk.exe" } ## Upload the Master Image VHD resource "azurerm_storage_blob" "CC-AZ-SB-MI" { depends_on = [time_sleep.wait_3_minutes] name = "${var.azurerm-UploadVHD-name}" storage_account_name = azurerm_storage_account.CC-AZ-SB.name storage_container_name = azurerm_storage_container.CC-AZ-SB.name type = "Page" source = "${var.azurerm-UploadVHD-source}" } ## Write the variables into the next following module CConAzCreate.main.auto.tfvars.json resource "local_file" "OutputAllVars" { depends_on = [time_sleep.wait_5_minutes] content = jsonencode( { "azurerm-clientid": "${var.azurerm-clientid}", "azurerm-clientsecret": "${var.azurerm-clientsecret}", "azurerm-tenantid": "${var.azurerm-tenantid}", "azurerm-subscriptionid": "${var.azurerm-subscriptionid}", "azurerm-resource_group_name": azurerm_resource_group.CC-AZ-TF.name, "azurerm-resource_group_location": azurerm_resource_group.CC-AZ-TF.location, "azurerm-storage_account_name": azurerm_storage_account.CC-AZ-SB.name, "azurerm-storage_account_location": azurerm_storage_account.CC-AZ-SB.location, "azurerm-storage_account_tier": azurerm_storage_account.CC-AZ-SB.account_tier, "azurerm-storage_account_replication": azurerm_storage_account.CC-AZ-SB.account_replication_type, "azurerm-storage_account_container_name": azurerm_storage_container.CC-AZ-SB.name, "cc-customerid": "${var.cc-customerid}", "azurerm-masterimage-name": "${var.azurerm-UploadVHD-name}", "azurerm-masterimage-storage_account_type": "${var.azurerm-UploadVHD-type}", "azurerm-masterimage-create_option": "Import", "azurerm-masterimage-source_uri": azurerm_storage_blob.CC-AZ-SB-MI.id, "azurerm-masterimage-storage_account_id" :azurerm_storage_account.CC-AZ-SB.id, "azurerm-masterimage-os_type": "Windows", "azurerm-masterimage-hyper_v_generation": "V2" } ) filename = "${path.root}/../__CConAzureCreation/CConAzCreate-main.auto.tfvars.json" }
Module 2: CConAzureCreation
This is the Terraform configuration file for Module 2
# Begin creation of the Citrix Cloud Resource Location on Azure # Create all needed Azure-based resources for deploying a Citrix Cloud Resource Location ## Local variables for DC configuration locals { Import_PS_Cmdlet_Command = "Import-Module ADDSDeployment" Secure_Password_Command = "$password = ConvertTo-SecureString ${var.azurerm-vm-domainadmin-pw} -AsPlainText -Force" Install_ADDSonDC_Command = "Add-WindowsFeature -name ad-domain-services -IncludeManagementTools" Configure_ADDSonDC_Command = "Install-ADDSForest -CreateDnsDelegation:$false -DomainNetbiosName ${var.azurerm-vm-netbiosname} -DomainMode Win2012R2 -ForestMode Win2012R2 -InstallDns:$true -SafeModeAdministratorPassword $password -DomainName ${var.azurerm-vm-domainname} -Force:$true" Shutdown_VM_Command = "shutdown -r -t 10" Wanted_Exit_Code = "exit 0" Nested_PowerShell_Command = "${local.Import_PS_Cmdlet_Command}; ${local.Secure_Password_Command}; ${local.Install_ADDSonDC_Command}; ${local.Configure_ADDSonDC_Command}; ${local.Shutdown_VM_Command}; ${local.Wanted_Exit_Code}" locvnetlist = tolist(split(", ", var.azurerm-vnet-addressspace-single)) locsubnetlist = tolist(split(", ", var.azurerm-subnet-addressprefix-single)) } ## Create 2 Cloud Connector VMs in Azure in a dedicated Resource Group ### Create a dedicated Virtual Network #### Set the values in the corresponding .auto.tfvars.json file resource "azurerm_virtual_network" "CC-AZ-TF" { name = "${var.azurerm-vnet-name}" address_space = local.locvnetlist location = "${var.azurerm-resource_group_location}" resource_group_name = "${var.azurerm-resource_group_name}" } resource "azurerm_virtual_network_dns_servers" "CC-AZ-TF" { virtual_network_id = azurerm_virtual_network.CC-AZ-TF.id dns_servers = ["10.0.0.4"] } ### Create a dedicated Public IP for the CC1-VM #### Set the values in the corresponding .auto.tfvars.json file resource "azurerm_public_ip" "CC-AZ-TF" { name = "${var.azurerm-pubip-name}" location = "${var.azurerm-resource_group_location}" resource_group_name = "${var.azurerm-resource_group_name}" allocation_method = "${var.azurerm-pubip-allocation}" } ### Create a dedicated Public IP for the CC2-VM #### Set the values in the corresponding .auto.tfvars.json file resource "azurerm_public_ip" "CC-AZ-TF2" { name = "${var.azurerm-pubip-name2}" location = "${var.azurerm-resource_group_location}" resource_group_name = "${var.azurerm-resource_group_name}" allocation_method = "${var.azurerm-pubip-allocation}" } ### Create a dedicated Public IP for the DC-VM #### Set the values in the corresponding .auto.tfvars.json file resource "azurerm_public_ip" "CC-AZ-TF-DC" { name = "${var.azurerm-pubip-name-dc}" location = "${var.azurerm-resource_group_location}" resource_group_name = "${var.azurerm-resource_group_name}" allocation_method = "${var.azurerm-pubip-allocation}" } ### Create a dedicated Subnet for the VMs #### Set the values in the corresponding .auto.tfvars.json file resource "azurerm_subnet" "CC-AZ-TF" { name = "${var.azurerm-subnet-name}" resource_group_name = "${var.azurerm-resource_group_name}" virtual_network_name = azurerm_virtual_network.CC-AZ-TF.name address_prefixes = ["10.0.0.0/24"] } ### Create a dedicated Network Security Group and the Firewall rules for the VMs #### Set the values in the corresponding .auto.tfvars.json file resource "azurerm_network_security_group" "CC-AZ-TF" { name = "${var.azurerm-nsg-name}" location = "${var.azurerm-resource_group_location}" resource_group_name = "${var.azurerm-resource_group_name}" security_rule { name = "${var.azurerm-nsg-securityrule1.name}" priority = "${var.azurerm-nsg-securityrule1.priority}" direction = "${var.azurerm-nsg-securityrule1.direction}" access = "${var.azurerm-nsg-securityrule1.access}" protocol = "${var.azurerm-nsg-securityrule1.protocol}" source_port_range = "${var.azurerm-nsg-securityrule1.source_port_range}" destination_port_range = "${var.azurerm-nsg-securityrule1.destination_port_range}" source_address_prefix = "${var.azurerm-nsg-securityrule1.source_address_prefix}" destination_address_prefix = "${var.azurerm-nsg-securityrule1.destination_address_prefix}" } security_rule { name = "${var.azurerm-nsg-securityrule2.name}" priority = "${var.azurerm-nsg-securityrule2.priority}" direction = "${var.azurerm-nsg-securityrule2.direction}" access = "${var.azurerm-nsg-securityrule2.access}" protocol = "${var.azurerm-nsg-securityrule2.protocol}" source_port_range = "${var.azurerm-nsg-securityrule2.source_port_range}" destination_port_range = "${var.azurerm-nsg-securityrule2.destination_port_range}" source_address_prefix = "${var.azurerm-nsg-securityrule2.source_address_prefix}" destination_address_prefix = "${var.azurerm-nsg-securityrule2.destination_address_prefix}" } security_rule { name = "${var.azurerm-nsg-securityrule3.name}" priority = "${var.azurerm-nsg-securityrule3.priority}" direction = "${var.azurerm-nsg-securityrule3.direction}" access = "${var.azurerm-nsg-securityrule3.access}" protocol = "${var.azurerm-nsg-securityrule3.protocol}" source_port_range = "${var.azurerm-nsg-securityrule3.source_port_range}" destination_port_range = "${var.azurerm-nsg-securityrule3.destination_port_range}" source_address_prefix = "${var.azurerm-nsg-securityrule3.source_address_prefix}" destination_address_prefix = "${var.azurerm-nsg-securityrule3.destination_address_prefix}" } security_rule { name = "${var.azurerm-nsg-securityrule4.name}" priority = "${var.azurerm-nsg-securityrule4.priority}" direction = "${var.azurerm-nsg-securityrule4.direction}" access = "${var.azurerm-nsg-securityrule4.access}" protocol = "${var.azurerm-nsg-securityrule4.protocol}" source_port_range = "${var.azurerm-nsg-securityrule4.source_port_range}" destination_port_range = "${var.azurerm-nsg-securityrule4.destination_port_range}" source_address_prefix = "${var.azurerm-nsg-securityrule4.source_address_prefix}" destination_address_prefix = "${var.azurerm-nsg-securityrule4.destination_address_prefix}" } } ### Associate the Network Security Group and the Firewall rules with the Subnet resource "azurerm_subnet_network_security_group_association" "CC-AZ-TF" { # depends_on = [ azurerm_subnet.CC-AZ-TF ] subnet_id = azurerm_subnet.CC-AZ-TF.id network_security_group_id = azurerm_network_security_group.CC-AZ-TF.id } ### Create a dedicated Network Interface and IP configuration for the DC-VM #### Set the values in the corresponding .auto.tfvars.json file resource "azurerm_network_interface" "CC-AZ-TF-DC" { name = "${var.azurerm-nic-name-dc}" location = "${var.azurerm-resource_group_location}" resource_group_name = "${var.azurerm-resource_group_name}" dns_servers = ["10.0.0.4"] ip_configuration { name = "${var.azurerm-nic-ipconfiguration-dc.name}" subnet_id = azurerm_subnet.CC-AZ-TF.id private_ip_address_allocation = "${var.azurerm-nic-ipconfiguration-dc.private_ip_address_allocation}" public_ip_address_id = azurerm_public_ip.CC-AZ-TF-DC.id } } ### Create a dedicated Network Interface and IP configuration for the CC1-VM #### Set the values in the corresponding .auto.tfvars.json file resource "azurerm_network_interface" "CC-AZ-TF" { name = "${var.azurerm-nic-name}" location = "${var.azurerm-resource_group_location}" resource_group_name = "${var.azurerm-resource_group_name}" dns_servers = azurerm_network_interface.CC-AZ-TF-DC.private_ip_addresses ip_configuration { name = "${var.azurerm-nic-ipconfiguration.name}" subnet_id = azurerm_subnet.CC-AZ-TF.id private_ip_address_allocation = "${var.azurerm-nic-ipconfiguration.private_ip_address_allocation}" public_ip_address_id = azurerm_public_ip.CC-AZ-TF.id } } ### Create a dedicated Network Interface and IP configuration for the CC2-VM #### Set the values in the corresponding .auto.tfvars.json file resource "azurerm_network_interface" "CC-AZ-TF2" { name = "${var.azurerm-nic-name2}" location = "${var.azurerm-resource_group_location}" resource_group_name = "${var.azurerm-resource_group_name}" dns_servers = azurerm_network_interface.CC-AZ-TF-DC.private_ip_addresses ip_configuration { name = "${var.azurerm-nic-ipconfiguration2.name}" subnet_id = azurerm_subnet.CC-AZ-TF.id private_ip_address_allocation = "${var.azurerm-nic-ipconfiguration2.private_ip_address_allocation}" public_ip_address_id = azurerm_public_ip.CC-AZ-TF2.id } } ### Create the DC-VM resource "azurerm_windows_virtual_machine" "CC-AZ-TF-DC" { depends_on = [ azurerm_network_interface.CC-AZ-TF-DC ] name = "${var.azurerm-vm-name-dc}" location = "${var.azurerm-resource_group_location}" resource_group_name = "${var.azurerm-resource_group_name}" network_interface_ids = [azurerm_network_interface.CC-AZ-TF-DC.id] size = "${var.azurerm-vm-size}" admin_username = "${var.azurerm-vm-adun}" admin_password = "${var.azurerm-vm-adpw}" provision_vm_agent = true source_image_reference { publisher = "${var.azurerm-vm-sourceimage-publisher}" offer = "${var.azurerm-vm-sourceimage-offer}" sku = "${var.azurerm-vm-sourceimage-sku}" version = "${var.azurerm-vm-sourceimage-version}" } os_disk { name = "${var.azurerm-vm-osdisk-name-dc}" caching = "${var.azurerm-vm-osdisk-cache}" storage_account_type = "${var.azurerm-vm-osdisk-storagetype}" } } ### Create the CC1-VM resource "azurerm_windows_virtual_machine" "CC-AZ-TF" { depends_on = [ azurerm_network_interface.CC-AZ-TF ] name = "${var.azurerm-vm-name}" location = "${var.azurerm-resource_group_location}" resource_group_name = "${var.azurerm-resource_group_name}" network_interface_ids = [azurerm_network_interface.CC-AZ-TF.id] size = "${var.azurerm-vm-size}" admin_username = "${var.azurerm-vm-adun}" admin_password = "${var.azurerm-vm-adpw}" provision_vm_agent = true source_image_reference { publisher = "${var.azurerm-vm-sourceimage-publisher}" offer = "${var.azurerm-vm-sourceimage-offer}" sku = "${var.azurerm-vm-sourceimage-sku}" version = "${var.azurerm-vm-sourceimage-version}" } os_disk { name = "${var.azurerm-vm-osdisk-name}" caching = "${var.azurerm-vm-osdisk-cache}" storage_account_type = "${var.azurerm-vm-osdisk-storagetype}" } } ### Create the CC2-VM resource "azurerm_windows_virtual_machine" "CC-AZ-TF2" { depends_on = [ azurerm_network_interface.CC-AZ-TF2 ] name = "${var.azurerm-vm-name2}" location = "${var.azurerm-resource_group_location}" resource_group_name = "${var.azurerm-resource_group_name}" network_interface_ids = [azurerm_network_interface.CC-AZ-TF2.id] size = "${var.azurerm-vm-size}" admin_username = "${var.azurerm-vm-adun}" admin_password = "${var.azurerm-vm-adpw}" provision_vm_agent = true source_image_reference { publisher = "${var.azurerm-vm-sourceimage-publisher}" offer = "${var.azurerm-vm-sourceimage-offer}" sku = "${var.azurerm-vm-sourceimage-sku}" version = "${var.azurerm-vm-sourceimage-version}" } os_disk { name = "${var.azurerm-vm-osdisk-name2}" caching = "${var.azurerm-vm-osdisk-cache}" storage_account_type = "${var.azurerm-vm-osdisk-storagetype}" } } ## Create the MasterImage Managed Disk resource "azurerm_managed_disk" "CreateManagedDiskFromUploadedVHD" { name = var.azurerm-masterimage-name location = var.azurerm-resource_group_location resource_group_name = var.azurerm-resource_group_name storage_account_type = var.azurerm-masterimage-storage_account_type create_option = var.azurerm-masterimage-create_option source_uri = var.azurerm-masterimage-source_uri storage_account_id = var.azurerm-masterimage-storage_account_id os_type = var.azurerm-masterimage-os_type hyper_v_generation = var.azurerm-masterimage-hyper_v_generation } ### Create an object for 5mins waiting time resource "time_sleep" "wait_5_minutes" { create_duration = "300s" } ### Create the Custom Script Extension to create a new Forest and Domain on DC-VM resource "azurerm_virtual_machine_extension" "create-active-directory-forest" { depends_on = [ azurerm_windows_virtual_machine.CC-AZ-TF-DC, time_sleep.wait_5_minutes ] name = "create-active-directory-forest" virtual_machine_id = azurerm_windows_virtual_machine.CC-AZ-TF-DC.id publisher = "Microsoft.Compute" type = "CustomScriptExtension" type_handler_version = "1.9" settings = <<SETTINGS { "commandToExecute": "powershell.exe -Command \"${local.Nested_PowerShell_Command}\"" } SETTINGS } ################################## All Azure-related Resources have been created, now let´s start with Citrix Cloud ################################## # Create Citrix Environment resource "random_uuid" "IDforCCRL" { } ## Create PowerShell command to be run on the CC machines locals { randomuuid = random_uuid.IDforCCRL.result #powershell_command = "New-AzStorageContext -StorageAccountName ${var.azurerm-storage_account_name} -Anonymous -Protocol https | Get-AzStorageBlobContent -blob download.ps1 -container ${var.azurerm-storage_account_container_name} -destination c:/temp;c:/temp/download.ps1; c:/temp/InstallPreReqs.ps1" powershell_command = "Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force; Install-Module -Name PowerShellGet -Force; Invoke-WebRequest -Uri https://satuzyo2lp7eh7j.blob.core.windows.net/scsatuzyo2lp7eh7j/download.ps1 -OutFile c:/temp/download.ps1; c:/temp/download.ps1; c:/temp/InstallPreReqs.ps1" # >powershell.exe -ExecutionPolicy Unrestricted -Command "Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force; Install-Module -Name PowerShellGet -Force; Invoke-WebRequest -Uri https://satuzyo2lp7eh7j.blob.core.windows.net/scsatuzyo2lp7eh7j/download.ps1 -OutFile c:/temp/download.ps1; c:/temp/download.ps1; c:/temp/InstallPreReqs.ps1" } ## Create a dedicated Resource Location in Citrix Cloud resource "restapi_object" "CreateRL" { provider = restapi.restapi_rl path="/resourcelocations" data = jsonencode( { "id" = "${local.randomuuid}", "name" = "${var.cc-restrl-name}", "internalOnly" = false, "timeZone" = "GMT Standard Time", "readOnly" = false } ) } ## Create PowerShell files with configuration and next steps and save it into Transfer directory ### 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 = "${path.module}/cwc.json" } ### Create PowerShell file for downloading the CWC files to the local CC-VM resource "local_file" "Download-ps1" { content = <<-EOT Start-Sleep -Seconds 60 Invoke-WebRequest -Uri https://${var.azurerm-storage_account_name}.blob.core.windows.net/${var.azurerm-storage_account_container_name}/cwcconnector.exe -OutFile c:/temp/cwcconnector.exe Invoke-WebRequest -Uri https://${var.azurerm-storage_account_name}.blob.core.windows.net/${var.azurerm-storage_account_container_name}/cwc.json -OutFile c:/temp/cwc.json Invoke-WebRequest -Uri https://${var.azurerm-storage_account_name}.blob.core.windows.net/${var.azurerm-storage_account_container_name}/InstallPreReqs.ps1 -OutFile c:/temp/InstallPreReqs.ps1 Invoke-WebRequest -Uri https://${var.azurerm-storage_account_name}.blob.core.windows.net/${var.azurerm-storage_account_container_name}/InstallCWC.ps1 -OutFile c:/temp/InstallCWC.ps1 Invoke-WebRequest -Uri https://${var.azurerm-storage_account_name}.blob.core.windows.net/${var.azurerm-storage_account_container_name}/CitrixPoshSdk.exe -OutFile c:/temp/CitrixPoshSdk.exe Set-ItemProperty -Path "HKLM:\Software\Microsoft\Windows\CurrentVersion\RunOnce" -Name "install" -Value "powershell.exe -ExecutionPolicy Unrestricted -file c:/temp/InstallPreReqs.ps1" Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" -Name "AutoAdminLogon" -Value "1" Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" -Name "DefaultUsername" -Value "xxxxxxxxxx" Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" -Name "DefaultPassword" -Value "xxxxxxxxxx" Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" -Name "DefaultDomain" -Value "tf.the-austrian-citrix-guy.at" Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" -Name "AutoLogonCount" -Value "1" netdom.exe join $env:COMPUTERNAME /domain:tf.the-austrian-citrix-guy.at /UserD:xxxxxxxxxx@tf /PasswordD:xxxxxxxxxx /reboot:5 EOT filename = "${path.module}/download.ps1" } ### Create PowerShell file for installing the Citrix Remote PoSH SDK - we need to determine the correct RL-ID resource "local_file" "InstallPreReqs-ps1" { content = <<-EOT cd /temp Add-Content C:\temp\log.txt "`nScript started." # Install Citrix Remote PowerShell SDK ./CitrixPoSHSDK.exe -quiet Add-Content C:\temp\log.txt "`nPowerShell SDK installed." # Timeout to settle all processes Start-Sleep -Seconds 180 Add-Content C:\temp\log.txt "`nTimeout elapsed." # Correct the Resource Location ID in cwc.json file add-pssnapin Citrix.* Set-XDCredentials -StoreAs default -ProfileType CloudApi -CustomerId ${var.cc-customerid} -APIKey ${var.cc-apikey-clientId} -SecretKey ${var.cc-apikey-clientSecret} $filter = "${var.cc-restrl-name}" $var = Get-Configzone |Select Name, ExternalUID | Where-Object {$_.Name -like $filter} $uid = $var.ExternalUid (Get-Content cwc.json -Raw) -replace 'XXXXXXXXXX', $uid | Set-Content cwc.json Add-Content C:\temp\log.txt "`ncwc.json corrected." # Remove Citrix Remote PowerShell SDK as it is not compatible with Cloud Controller $CtxPoSHSDK = Get-WmiObject -Class Win32_Product | Where-Object{$_.Name -Like "Citrix*"} if ($CtxPoSHSDK -ne $null) { $CtxPoSHSDK.Uninstall() } Add-Content C:\temp\log.txt "`nPowerShell SDK removed." # Install Citrix Cloud Controller based on the cwc.json configuration file ./CWCConnector.exe /q /ParametersFilePath:c:/temp/cwc.json EOT filename = "${path.module}/InstallPreReqs.ps1" } ### Create PowerShell file for silently uninstalling the Citrix Remote PoSH SDK as this would break CWCConnector-Installation resource "local_file" "InstallCWC-ps1" { content = <<-EOT $CtxPoSHSDK = Get-WmiObject -Class Win32_Product | Where-Object{$_.Name -Like "Citrix*"} if ($CtxPoSHSDK -ne $null) { $CtxPoSHSDK.Uninstall() } cd /temp ./CWCConnector.exe /q /ParametersFilePath:c:/temp/cwc.json EOT filename = "${path.module}/InstallCWC.ps1" } ### Download CWC installer and save it into Transfer directory resource "terraform_data" "Download-CWC-Installer" { provisioner "local-exec" { command = "curl -s -o ${path.module}/cwcconnector.exe https://downloads.cloud.com/${var.cc-customerid}/connector/cwcconnector.exe" } } ## Create object for 3min waiting time - needed for Azure backend to complete its configuration resource "time_sleep" "wait_1_minutes" { create_duration = "60s" } ## Create object for 3min waiting time - needed for Azure backend to complete its configuration resource "time_sleep" "wait_3_minutes" { create_duration = "180s" } ## Create object for 4min waiting time - needed for Azure backend to complete its configuration resource "time_sleep" "wait_4_minutes" { create_duration = "240s" } resource "time_sleep" "wait_2_minutes_after_adcompleted" { depends_on = [azurerm_virtual_machine_extension.create-active-directory-forest] create_duration = "120s" } ## Create upload for Download-Script to Storage Blob resource "azurerm_storage_blob" "CC-AZ-SB-DL" { depends_on = [time_sleep.wait_1_minutes] name = "download.ps1" storage_account_name = "${var.azurerm-storage_account_name}" storage_container_name = "${var.azurerm-storage_account_container_name}" type = "Block" source = "download.ps1" } ## Create upload for Install-CloudConnector-Script to Storage Blob resource "azurerm_storage_blob" "CC-AZ-SB-Install" { depends_on = [time_sleep.wait_1_minutes] name = "InstallCWC.ps1" storage_account_name = "${var.azurerm-storage_account_name}" storage_container_name = "${var.azurerm-storage_account_container_name}" type = "Block" source = "InstallCWC.ps1" } ## Create upload for CloudConnector-Configuration Script to Storage Blob resource "azurerm_storage_blob" "CC-AZ-SB-JSON" { depends_on = [time_sleep.wait_1_minutes] name = "cwc.json" storage_account_name = "${var.azurerm-storage_account_name}" storage_container_name = "${var.azurerm-storage_account_container_name}" type = "Block" source = "cwc.json" } ## Create upload for Prerequisites-Script to Storage Blob resource "azurerm_storage_blob" "CC-AZ-SB-InstallPreReqs-ps1" { depends_on = [time_sleep.wait_1_minutes] name = "InstallPreReqs.ps1" storage_account_name = "${var.azurerm-storage_account_name}" storage_container_name = "${var.azurerm-storage_account_container_name}" type = "Block" source = "InstallPreReqs.ps1" } ## Write the variables into the next following module CConAzureCitrixCloudStuff.main.auto.tfvars.json resource "local_file" "OutputAllVars" { depends_on = [time_sleep.wait_5_minutes] content = jsonencode( { "azurerm-clientid": "${var.azurerm-clientid}", "azurerm-clientsecret": "${var.azurerm-clientsecret}", "azurerm-tenantid": "${var.azurerm-tenantid}", "azurerm-subscriptionid": "${var.azurerm-subscriptionid}", "azurerm-resource_group_name": "${var.azurerm-resource_group_name}", "azurerm-resource_group_location": "${var.azurerm-resource_group_location}" "azurerm-vnet-id": azurerm_virtual_network.CC-AZ-TF.id, "azurerm-subnet-id": azurerm_subnet.CC-AZ-TF.id, "cc-restrl-name": "${var.cc-restrl-name}", "cc-customerid": "${var.cc-customerid}", "cc-apikey-clientId": "${var.cc-apikey-clientId}", "cc-apikey-clientSecret": "${var.cc-apikey-clientSecret}", "cc-apikey-type": "client_credentials", } ) filename = "${path.root}/../__CConAzureCitrixCloudStuff/CConAzCitrixCloudStuff-main.auto.tfvars.json" } ## Create VM extension to download the PowerShell script containing the next steps and execute them on CC1 resource "azurerm_virtual_machine_extension" "DownloadPSScriptToLocalVMAndExecuteItOnCC1" { depends_on = [time_sleep.wait_2_minutes_after_adcompleted] name = "DownloadPSScriptToLocalVMAndExecuteIt" virtual_machine_id = azurerm_windows_virtual_machine.CC-AZ-TF.id publisher = "Microsoft.Compute" type = "CustomScriptExtension" type_handler_version = "1.9" settings = <<SETTINGS { "commandToExecute": "powershell.exe -ExecutionPolicy Unrestricted -Command \"${local.powershell_command}\"" } SETTINGS } ## Create VM extension to download the PowerShell script containing the next steps and execute them on CC2 resource "azurerm_virtual_machine_extension" "DownloadPSScriptToLocalVMAndExecuteItOnCC2" { depends_on = [time_sleep.wait_2_minutes_after_adcompleted] name = "DownloadPSScriptToLocalVMAndExecuteIt" virtual_machine_id = azurerm_windows_virtual_machine.CC-AZ-TF2.id publisher = "Microsoft.Compute" type = "CustomScriptExtension" type_handler_version = "1.9" settings = <<SETTINGS { "commandToExecute": "powershell.exe -ExecutionPolicy Unrestricted -Command \"${local.powershell_command}\"" } SETTINGS }
Module 3: CConAzureCitrixCloudStuff
This is the Terraform configuration file for Module 3
# Creation of the Citrix Cloud Machine Catalog ## Local variables for Zone retrieval ### Create PowerShell file for determining the SiteID resource "local_file" "GetSiteIDScript" { content = <<-EOT $requestUri = "https://api-eu.cloud.com/cvad/manage/me" $headers = @{ "Accept"="application/json"; "Authorization" = "${var.cc-apikey-bearer}"; "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 = "${path.module}/GetSiteID.txt" Set-Content -Path $PathCompl -Value $SiteID EOT filename = "${path.module}/GetSiteID.ps1" } ### Running the SiteID-Script to generate the SiteID resource "terraform_data" "SiteID" { depends_on = [ local_file.GetSiteIDScript ] provisioner "local-exec" { command = "GetSiteID.ps1" interpreter = ["PowerShell", "-File"] } } ### Retrieving the SiteID data "local_file" "input_site" { depends_on = [ terraform_data.SiteID ] filename = "${path.module}/GetSiteID.txt" } ### Create PowerShell file for determining the ZoneID resource "local_file" "GetZoneIDScript" { depends_on = [ data.local_file.input_site ] content = <<-EOT $requestUri = "https://api-eu.cloud.com/cvad/manage/Zones" $headers = @{ "Accept"="application/json"; "Authorization" = "${var.cc-apikey-bearer}"; "Citrix-CustomerId" = "${var.cc-customerid}"; "Citrix-InstanceId" = "${data.local_file.input_site.content}" } $response = Invoke-RestMethod -Uri $requestUri -Method GET -Headers $headers | Convertto-Json $responsedejson = $response | ConvertFrom-Json $ZoneId = $responsedejson.Items | Where-Object { $_.Name -eq "${var.cc-restrl-name}" } | Select-Object id $Export1 = $ZoneId -replace("@{Id=","") $ZoneID = $Export1 -replace("}","") $PathCompl = "${path.module}/GetZoneID.txt" Set-Content -Path $PathCompl -Value $ZoneID EOT filename = "${path.module}/GetZoneID.ps1" } ### Running the ZoneID-Script to generate the ZoneID resource "terraform_data" "ZoneID" { depends_on = [ local_file.GetZoneIDScript ] provisioner "local-exec" { command = "GetZoneID.ps1" interpreter = ["PowerShell", "-File"] } } ### Retrieving the ZoneID data "local_file" "input_zone" { depends_on = [ terraform_data.ZoneID ] filename = "${path.module}/GetZoneID.txt" } # Creation of the Citrix Cloud Hypervisor Connection and Hypervisor Connection Resource Pool ## Local variables for MC configuration locals { } ## Create a Hypervisor Connection Resource Pool based on the previously created Hypervisor Connection ### Set the values in the corresponding CConAzCitrixCloudStuff-main.auto.tfvars.json file resource "citrix_daas_hypervisor" "TF-CC-HYPV" { name = "${var.cc-hyp-name}" connection_type = "${var.cc-hyp-connectiontype}" zone = "${var.cc-restrl-id}" active_directory_id = "${var.azurerm-tenantid}" subscription_id = "${var.azurerm-subscriptionid}" application_secret = "${var.azurerm-clientsecret}" application_id = "${var.azurerm-clientid}" } ## Create a Hypervisor Connection Resource Pool based on the previously created Hypervisor Connection ### Set the values in the corresponding CConAzCitrixCloudStuff-hyp.auto.tfvars.json file resource "citrix_daas_hypervisor_resource_pool" "TF-CC-HYPV-POOL" { name = "${var.cc-hyp-pool-name}" hypervisor = citrix_daas_hypervisor.TF-CC-HYPV.id region = "${var.azurerm-resource_group_location}" virtual_network_resource_group = "${var.azurerm-resource_group_name}" virtual_network = "${var.azurerm-vnet-name}" subnets = [ "${var.azurerm-subnet-name}" ] } # Creation of the Citrix Cloud Machine Catalog ## Local variables for MC configuration locals { } ## Create a Machine Catalog based on the previously created entities ### Set the values in the corresponding CConAzCitrixCloudStuff-mc.auto.tfvars.json file resource "citrix_daas_machine_catalog" "TF-CC-MC" { depends_on = [ citrix_daas_hypervisor_resource_pool.TF-CC-HYPV-POOL ] name = "${var.cc-mc-name}" description = "${var.cc-mc-description}" zone = citrix_daas_hypervisor.TF-CC-HYPV.zone service_account = "${var.azurerm-vm-domainAdminUsernameWithoutDomain}" service_account_password = "${var.azurerm-vm-domainAdminPassword}" allocation_type = "${var.cc-mc-allocationtype}" # "Random" session_support = "${var.cc-mc-sessiontype}" # "SingleSession" provisioning_scheme = { machine_config = { hypervisor = citrix_daas_hypervisor.TF-CC-HYPV.id hypervisor_resource_pool = citrix_daas_hypervisor_resource_pool.TF-CC-HYPV-POOL.id service_offering = "${var.cc-mc-service_offering}" resource_group = "${var.azurerm-resource_group_name}" #storage_account = "${var.azurerm-storage_account_name}" #container = "${var.azurerm-storage_account_container_name}" master_image = "${var.azurerm-masterimage-name}" #machine_snapshot = "${var.azurerm-masterimage-name-ss}" } network_mapping = { network_device = "0" network = "${var.azurerm-subnet-name}" } number_of_total_machines = "${var.cc-mc-machine_count}" machine_account_creation_rules = { naming_scheme = "${var.cc-mc-naming_scheme_name}" naming_scheme_type = "${var.cc-mc-naming_scheme_type}" domain = "${var.azurerm-vm-domainname}" #main_ou = "${var.cc-dg-domain-ou}" } os_type = "${var.cc-mc-os_type}" storage_type = "${var.cc-mc-storage_type}" use_managed_disks = "${var.cc-mc-use_managed_disks}" /* writeback_cache = { wbc_disk_storage_type = "${var.cc-mc-wbc_disk_storage_type}" persist_wbc = "${var.cc-mc-wbc_persistence}" persist_os_disk = "${var.cc-mc-wbc_persistence_os}" persist_vm = "${var.cc-mc-wbc_persistence_vm}" writeback_cache_disk_size_gb = "${var.cc-mc-wbc_disk_size_gb}" storage_cost_saving = "${var.cc-mc-wbc_cost_saving}" } */ } } # Creation of the Citrix Cloud Delivery Group ## Local variables for DG configuration locals { } ## Create object for 2min waiting time - needed for backend operations to complete its configurations resource "time_sleep" "wait_2_minutes_dg" { create_duration = "120s" } ## Create a Delivery Group based on the previously created entities ### Set the values in the corresponding dg.auto.tfvars.json file resource "citrix_daas_delivery_group" "TF-CC-DG" { depends_on = [ citrix_daas_machine_catalog.TF-CC-MC, time_sleep.wait_2_minutes_dg ] name = "${var.cc-dg-name}" description = "${var.cc-dg-description}" associated_machine_catalogs = [ { machine_catalog = citrix_daas_machine_catalog.TF-CC-MC.id machine_count = "${var.cc-mc-machine_count}" } ] autoscale_enabled = "${var.cc-dg-autoscale_usage}" users = var.cc-dg-users-upn autoscale_settings = { autoscale_display_name = "${var.cc-dg-autoscale_display_name}" disconnect_peak_idle_session_after_seconds = "${var.cc-dg-autoscale_disconnect_peak_idle_session_after_seconds}" disconnect_off_peak_idle_session_after_seconds = "${var.cc-dg-autoscale_disconnect_off_peak_idle_session_after_seconds}" log_off_peak_disconnected_session_after_seconds = "${var.cc-dg-autoscale_log_off_peak_disconnected_session_after_seconds}" log_off_off_peak_disconnected_session_after_seconds = "${var.cc-dg-autoscale_log_off_off_peak_disconnected_session_after_seconds}" #peak_disconnect_action = "${var.cc-dg-autoscale_peak_disconnect_action}" #off_peak_disconnect_action = "${var.cc-dg-autoscale_off_peak_disconnect_action}" #peak_log_off_action = "${var.cc-dg-autoscale_peak_log_off_action}" #off_peak_log_off_action = "${var.cc-dg-autoscale_off_peak_log_off_action}" #peak_disconnect_timeout_minutes = "${var.cc-dg-autoscale_peak_disconnect_timeout_minutes}" #off_peak_disconnect_timeout_minutes = "${var.cc-dg-autoscale_off_peak_disconnect_timeout_minutes}" peak_buffer_size_percent = "${var.cc-dg-autoscale_peak_buffer_size_percent}" off_peak_buffer_size_percent = "${var.cc-dg-autoscale_off_peak_buffer_size_percent}" power_time_schemes = [ { days_of_week = var.cc-dg-autoscale_days_of_week display_name = "${var.cc-dg-autoscale_display_name}" peak_time_ranges = var.cc-dg-autoscale_peak_time_ranges pool_size_schedules = [ { "TimeRange": "00:00-09:00", "PoolSize": 0 }, { "TimeRange": "09:00-17:00", "PoolSize": 1 }, { "TimeRange": "17:00-00:00", "PoolSize": 0 } ], pool_using_percentage = "${var.cc-dg-autoscale_pool_using_percentage}" }, ] } }
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 accountSign in
Already have an account? Sign in here.
Sign In Now