Merge pull request #76 from jensheerin/AVD

Add 101-azure-virtual-desktop
This commit is contained in:
Mark Gray (MSFT) 2022-02-15 11:08:36 -08:00 committed by GitHub
commit be9fb6ff53
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 1610 additions and 0 deletions

12
.gitignore vendored Normal file
View File

@ -0,0 +1,12 @@
log/
obj/
_site/
.optemp/
_themes*/
_repo.*/
.vs/
.vscode/
.idea
.ionide/
.openpublishing.buildcore.ps1.vscode/
*.DS_Store

View File

@ -0,0 +1,123 @@
## Terraform for Azure Virtual Desktop
The purpose of this repository is to demonstrate using Terraform to deploy a simple Azure Virtual Desktop environment. For Classic Azure Virtual Desktop click [here](https://github.com/Azure/RDS-Templates/tree/master/wvd-sh/terraform-azurerm-windowsvirtualdesktop).
## Requirements and limitations
* Ensure that you meet the [requirements for Azure Virtual Desktop](https://docs.microsoft.com/en-us/azure/virtual-desktop/overview#requirements)
* Terraform must be installed and configured as outlined [here](https://docs.microsoft.com/en-us/azure/developer/terraform/get-started-cloud-shell)
* Active Directory already in place in this example, we are using AD in its own VNet.
* Users in AAD that will be given access to AVD
* This demo does not support Azure ADDS only deployment
* Destroy could produce errors deleting subnet due to resources associated. Manually delete resources within the subnet before running destroy
## Components
* Azure Virtual Desktop Environment
* Networking Infrastructure
* Session Hosts
* Profile Storage
* Role Based Access Control
## Features
This directory contains the various components for building out Azure Virtual Desktop.
* `main.tf`
deploys a new workspace, hostpool, application group with associations
* `networking.tf`
deploys a new vnet, subnet, nsg and peering to AD vnet
* `host.tf`
deploys new session host from the marketplace build and join to domain
* `rbac.tf`
deploys rbac assignment for the users group
* `variables.tf`
Input variables
* `loganalytics.tf`
deploys log anaylytics workspace
* `sig.tf`
deploys log anaylytics workspace
* `random.tf`
Random provider configuration
* `defaults.tfvars`
declares the actual input values (keep security in mind if you are putting confidential data)
* `provider.tf`
Azure RM and Azure AD provider configuration
* `outputs.tf`
defines the outputs that will be displayed on deployment
* `netappstorage.tf`
as an alternate to Azure Files storage this deploys NetApp Files storage for profiles in a dedicated subnet (access needs to be granted to the ANF service) [Set up Azure NetApp Files](https://docs.microsoft.com/en-us/azure/azure-netapp-files/azure-netapp-files-quickstart-set-up-account-create-volumes?tabs=azure-portal)
## Variable Inputs
[Variable Inputs](USAGE.md#inputs)
## Deploy
If youve not previously setup terraform, check out this article to get it installed [Quickstart - Configure Terraform using Azure Cloud Shell](https://docs.microsoft.com/en-us/azure/developer/terraform/get-started-cloud-shell)
You can review our sample configuration video here
Once Terraform is setup and you have created your Terraform templates, the first step is to initialize Terraform. This step ensures that Terraform has all the prerequisites to build your template in Azure.
```
terraform init
```
The next step is to have Terraform review and validate the template. An execution plan is generated and stored in the file specified by the -out parameter.
We also need to pass our variable definitions file during the plan. We can either load it automatically by renaming env.tfvars as terraform.tfvars OR env.auto.tfvars, in which case we will use the following to create the execution plan:
```bash
terraform plan -out terraform_azure.tfplan
```
When you're ready to build the infrastructure in Azure, apply the execution plan:
```bash
terraform apply terraform_azure.tfplan
```
## Final Configuration
Youll notice we didnt actually configure the session hosts to use our profile storage at any point. There is an assumption that we are using GPO to manage FSLogix across our host pools as documented here: [Use FSLogix Group Policy Template Files - FSLogix](https://docs.microsoft.com/en-us/fslogix/use-group-policy-templates-ht).
At a minimum youll need to configure the registry keys to enable FSLogix and configure the VHD Location to the NetApp Share URI: [Profile Container registry configuration settings - FSLogix](https://docs.microsoft.com/en-us/fslogix/profile-container-configuration-reference#enabled)
## Troubleshooting Terraform deployment
<details>
<summary>Click to expand</summary>
Terraform deployment can fail in two main categories:
Issues with Terraform code
1. [Issues with Desired State Configuration (DSC)](#issues-with-desired-state-configuration-dsc)
2. [Issues with Terraform code](#issues-with-desired-state-configuration-dsc)
While it is rare to have issues with the Terraform code it is still possible, however most often errors are due to bad input in variables.tf.
* If there are errors in the Terraform code, please file a GitHub issue.
* If there are warning in the Terraform code feel free to ignore or address for your own instance of that code.
* Using Terraform error messages it's a good starting point towards identifying issues with input variables
### Issues with Desired State Configuration (DSC)
To troubleshoot this type of issue, navigate to the Azure portal and if needed reset the password on the VM that failed DSC. Once you are able to log in to the VM review the log files in the following two folders:
</details>
## Additional References
<details>
<summary>Click to expand</summary>
* [Terraform Download](https://www.terraform.io/downloads.html)
* [Visual Code Download](https://code.visualstudio.com/Download)
* [Powershell VS Code Extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode.PowerShell)
* [HashiCorp Terraform VS Code Extension](https://marketplace.visualstudio.com/items?itemName=HashiCorp.terraform)
* [Azure Terraform VS Code Extension Name](https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-azureterraform)
* [Azure CLI](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli-windows?tabs=azure-cli)
* [Configure the Azure Terraform Visual Studio Code extension](https://docs.microsoft.com/en-us/azure/developer/terraform/configure-vs-code-extension-for-terraform)
* [Setup video](https://youtu.be/YmbmpGdhI6w)
</details>

View File

@ -0,0 +1,102 @@
# Usage
<!--- BEGIN_TF_DOCS --->
## Requirements
| Name | Version |
|------|---------|
| <a name="requirement_azurerm"></a> [azurerm](#requirement\_azurerm) | ~>2.0 |
## Providers
| Name | Version |
|------|---------|
| <a name="provider_azuread"></a> [azuread](#provider\_azuread) | n/a |
| <a name="provider_azurerm"></a> [azurerm](#provider\_azurerm) | ~>2.0 |
| <a name="provider_random"></a> [random](#provider\_random) | n/a |
| <a name="provider_time"></a> [time](#provider\_time) | n/a |
## Modules
No modules.
## Resources
| Name | Type |
|------|------|
| [azuread_group.aad_group](https://registry.terraform.io/providers/hashicorp/azuread/latest/docs/resources/group) | resource |
| [azuread_group_member.aad_group_member](https://registry.terraform.io/providers/hashicorp/azuread/latest/docs/resources/group_member) | resource |
| [azurerm_log_analytics_workspace.law](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/log_analytics_workspace) | resource |
| [azurerm_network_interface.avd_vm_nic](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/network_interface) | resource |
| [azurerm_network_security_group.nsg](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/network_security_group) | resource |
| [azurerm_resource_group.log](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/resource_group) | resource |
| [azurerm_resource_group.rg](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/resource_group) | resource |
| [azurerm_resource_group.rg_storage](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/resource_group) | resource |
| [azurerm_resource_group.sigrg](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/resource_group) | resource |
| [azurerm_role_assignment.af_role](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/role_assignment) | resource |
| [azurerm_role_assignment.role](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/role_assignment) | resource |
| [azurerm_shared_image.example](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/shared_image) | resource |
| [azurerm_shared_image_gallery.sig](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/shared_image_gallery) | resource |
| [azurerm_storage_account.storage](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/storage_account) | resource |
| [azurerm_storage_share.FSShare](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/storage_share) | resource |
| [azurerm_subnet.subnet](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/subnet) | resource |
| [azurerm_subnet_network_security_group_association.nsg_assoc](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/subnet_network_security_group_association) | resource |
| [azurerm_virtual_desktop_application_group.dag](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/virtual_desktop_application_group) | resource |
| [azurerm_virtual_desktop_host_pool.hostpool](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/virtual_desktop_host_pool) | resource |
| [azurerm_virtual_desktop_workspace.workspace](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/virtual_desktop_workspace) | resource |
| [azurerm_virtual_desktop_workspace_application_group_association.ws-dag](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/virtual_desktop_workspace_application_group_association) | resource |
| [azurerm_virtual_machine_extension.domain_join](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/virtual_machine_extension) | resource |
| [azurerm_virtual_machine_extension.vmext_dsc](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/virtual_machine_extension) | resource |
| [azurerm_virtual_network.vnet](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/virtual_network) | resource |
| [azurerm_virtual_network_peering.peer1](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/virtual_network_peering) | resource |
| [azurerm_virtual_network_peering.peer2](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/virtual_network_peering) | resource |
| [azurerm_windows_virtual_machine.avd_vm](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/windows_virtual_machine) | resource |
| [random_string.AVD_local_password](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/string) | resource |
| [random_string.random](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/string) | resource |
| [time_rotating.avd_token](https://registry.terraform.io/providers/hashicorp/time/latest/docs/resources/rotating) | resource |
| [azuread_user.aad_user](https://registry.terraform.io/providers/hashicorp/azuread/latest/docs/data-sources/user) | data source |
| [azurerm_role_definition.role](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/role_definition) | data source |
| [azurerm_role_definition.storage_role](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/role_definition) | data source |
| [azurerm_virtual_network.ad_vnet_data](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/virtual_network) | data source |
## Inputs
| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| <a name="input_aad_group_name"></a> [aad\_group\_name](#input\_aad\_group\_name) | Azure Active Directory Group for AVD users | `string` | n/a | yes |
| <a name="input_ad_rg"></a> [ad\_rg](#input\_ad\_rg) | The resource group for AD VM | `string` | n/a | yes |
| <a name="input_ad_vnet"></a> [ad\_vnet](#input\_ad\_vnet) | Name of domain controller vnet | `string` | n/a | yes |
| <a name="input_avd_users"></a> [avd\_users](#input\_avd\_users) | AVD users | `list` | `[]` | no |
| <a name="input_deploy_location"></a> [deploy\_location](#input\_deploy\_location) | The Azure Region in which all resources in this example should be created. | `string` | n/a | yes |
| <a name="input_dns_servers"></a> [dns\_servers](#input\_dns\_servers) | Custom DNS configuration | `list(string)` | n/a | yes |
| <a name="input_domain_name"></a> [domain\_name](#input\_domain\_name) | Name of the domain to join | `string` | n/a | yes |
| <a name="input_domain_password"></a> [domain\_password](#input\_domain\_password) | Password of the user to authenticate with the domain | `string` | n/a | yes |
| <a name="input_domain_user_upn"></a> [domain\_user\_upn](#input\_domain\_user\_upn) | Username for domain join (do not include domain name as this is appended) | `string` | n/a | yes |
| <a name="input_hostpool"></a> [hostpool](#input\_hostpool) | Name of the Azure Virtual Desktop host pool | `string` | `"AVD-TF-HP"` | no |
| <a name="input_local_admin_password"></a> [local\_admin\_password](#input\_local\_admin\_password) | local admin password | `any` | n/a | yes |
| <a name="input_local_admin_username"></a> [local\_admin\_username](#input\_local\_admin\_username) | local admin username | `string` | n/a | yes |
| <a name="input_ou_path"></a> [ou\_path](#input\_ou\_path) | n/a | `string` | `""` | no |
| <a name="input_prefix"></a> [prefix](#input\_prefix) | Prefix of the name of the AVD machine(s) | `string` | n/a | yes |
| <a name="input_rdsh_count"></a> [rdsh\_count](#input\_rdsh\_count) | Number of AVD machines to deploy | `number` | `2` | no |
| <a name="input_rg_name"></a> [rg\_name](#input\_rg\_name) | Name of the Resource group in which to deploy these resources | `string` | `"AVD-TF"` | no |
| <a name="input_shared"></a> [shared](#input\_shared) | Prefix of the name of the AVD machine(s) | `string` | n/a | yes |
| <a name="input_subnet_range"></a> [subnet\_range](#input\_subnet\_range) | Address range for session host subnet | `list(string)` | n/a | yes |
| <a name="input_vm_size"></a> [vm\_size](#input\_vm\_size) | Size of the machine to deploy | `string` | `"Standard_DS2_v2"` | no |
| <a name="input_vnet_range"></a> [vnet\_range](#input\_vnet\_range) | Address range for deployment VNet | `list(string)` | n/a | yes |
| <a name="input_workspace"></a> [workspace](#input\_workspace) | Name of the Azure Virtual Desktop workspace | `string` | `"AVD TF Workspace"` | no |
## Outputs
| Name | Description |
|------|-------------|
| <a name="output_aadgroupname"></a> [aadgroupname](#output\_aadgroupname) | Azure Active Directory Group for AVD users |
| <a name="output_avdusers"></a> [avdusers](#output\_avdusers) | AVD users |
| <a name="output_dnsservers"></a> [dnsservers](#output\_dnsservers) | Custom DNS configuration |
| <a name="output_location"></a> [location](#output\_location) | The Azure region |
| <a name="output_rdshcount"></a> [rdshcount](#output\_rdshcount) | The number of VMs created |
| <a name="output_resource_group_name"></a> [resource\_group\_name](#output\_resource\_group\_name) | Name of the Resource group created |
| <a name="output_storage_account_share"></a> [storage\_account\_share](#output\_storage\_account\_share) | Name of the Azure File Share created for FSLogix |
| <a name="output_vnetrange"></a> [vnetrange](#output\_vnetrange) | Address range for deployment vnet |
<!--- END_TF_DOCS --->

View File

@ -0,0 +1,21 @@
# Customized the sample values below for your environment and either rename to terraform.tfvars or env.auto.tfvars
deploy_location = "west europe"
rg_name = "avd-resources-rg"
prefix = "avdtf"
local_admin_username = "localadm"
local_admin_password = "ChangeMe123$"
vnet_range = ["10.1.0.0/16"]
subnet_range = ["10.1.0.0/24"]
netapp_address = ["10.1.1.0/24"]
dns_servers = ["10.0.1.4", "168.63.129.16"]
aad_group_name = "AVDUsers"
domain_name = "infra.local"
domain_user_upn = "admin" # do not include domain name as this is appended
domain_password = "ChangeMe123!"
ad_vnet = "infra-network"
ad_rg = "infra-rg"
avd_users = [
"avduser01@infra.local",
"avduser01@infra.local"
]

View File

@ -0,0 +1,126 @@
locals {
registration_token = azurerm_virtual_desktop_host_pool.hostpool.registration_info[0].token
}
resource "random_string" "AVD_local_password" {
count = var.rdsh_count
length = 16
special = true
min_special = 2
override_special = "*!@#?"
}
resource "azurerm_network_interface" "avd_vm_nic" {
count = var.rdsh_count
name = "${var.prefix}-${count.index + 1}-nic"
resource_group_name = var.rg_name
location = var.deploy_location
ip_configuration {
name = "nic${count.index + 1}_config"
subnet_id = azurerm_subnet.subnet.id
private_ip_address_allocation = "dynamic"
}
depends_on = [
azurerm_resource_group.rg
]
}
resource "azurerm_windows_virtual_machine" "avd_vm" {
count = var.rdsh_count
name = "${var.prefix}-${count.index + 1}"
resource_group_name = var.rg_name
location = var.deploy_location
size = var.vm_size
network_interface_ids = ["${azurerm_network_interface.avd_vm_nic.*.id[count.index]}"]
provision_vm_agent = true
admin_username = var.local_admin_username
admin_password = var.local_admin_password
os_disk {
name = "${lower(var.prefix)}-${count.index + 1}"
caching = "ReadWrite"
storage_account_type = "Standard_LRS"
}
source_image_reference {
publisher = "MicrosoftWindowsDesktop"
offer = "Windows-10"
sku = "20h2-evd"
version = "latest"
}
depends_on = [
azurerm_resource_group.rg,
azurerm_network_interface.avd_vm_nic
]
}
resource "azurerm_virtual_machine_extension" "domain_join" {
count = var.rdsh_count
name = "${var.prefix}-${count.index + 1}-domainJoin"
virtual_machine_id = azurerm_windows_virtual_machine.avd_vm.*.id[count.index]
publisher = "Microsoft.Compute"
type = "JsonADDomainExtension"
type_handler_version = "1.3"
auto_upgrade_minor_version = true
settings = <<SETTINGS
{
"Name": "${var.domain_name}",
"OUPath": "${var.ou_path}",
"User": "${var.domain_user_upn}@${var.domain_name}",
"Restart": "true",
"Options": "3"
}
SETTINGS
protected_settings = <<PROTECTED_SETTINGS
{
"Password": "${var.domain_password}"
}
PROTECTED_SETTINGS
lifecycle {
ignore_changes = [settings, protected_settings]
}
depends_on = [
azurerm_virtual_network_peering.peer1,
azurerm_virtual_network_peering.peer2
]
}
resource "azurerm_virtual_machine_extension" "vmext_dsc" {
count = var.rdsh_count
name = "${var.prefix}${count.index + 1}-avd_dsc"
virtual_machine_id = azurerm_windows_virtual_machine.avd_vm.*.id[count.index]
publisher = "Microsoft.Powershell"
type = "DSC"
type_handler_version = "2.73"
auto_upgrade_minor_version = true
settings = <<-SETTINGS
{
"modulesUrl": "https://wvdportalstorageblob.blob.core.windows.net/galleryartifacts/Configuration_3-10-2021.zip",
"configurationFunction": "Configuration.ps1\\AddSessionHost",
"properties": {
"HostPoolName":"${azurerm_virtual_desktop_host_pool.hostpool.name}"
}
}
SETTINGS
protected_settings = <<PROTECTED_SETTINGS
{
"properties": {
"registrationInfoToken": "${local.registration_token}"
}
}
PROTECTED_SETTINGS
depends_on = [
azurerm_virtual_machine_extension.domain_join,
azurerm_virtual_desktop_host_pool.hostpool
]
}

View File

@ -0,0 +1,14 @@
resource "azurerm_resource_group" "log" {
name = "${var.shared}-resources"
location = var.deploy_location
}
# Creates Log Anaylytics Workspace
# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/log_analytics_workspace
resource "azurerm_log_analytics_workspace" "law" {
name = "log${random_string.random.id}"
location = azurerm_resource_group.log.location
resource_group_name = azurerm_resource_group.log.name
sku = "PerGB2018"
retention_in_days = 30
}

View File

@ -0,0 +1,54 @@
# Create AVD Resource Group
resource "azurerm_resource_group" "rg" {
name = var.rg_name
location = var.deploy_location
}
# Create AVD workspace
resource "azurerm_virtual_desktop_workspace" "workspace" {
name = var.workspace
resource_group_name = azurerm_resource_group.rg.name
location = var.deploy_location
friendly_name = "${var.prefix} Workspace"
description = "${var.prefix} Workspace"
}
resource "time_rotating" "avd_token" {
rotation_days = 30
}
# Create AVD host pool
resource "azurerm_virtual_desktop_host_pool" "hostpool" {
resource_group_name = azurerm_resource_group.rg.name
location = var.deploy_location
name = var.hostpool
friendly_name = var.hostpool
validate_environment = true
custom_rdp_properties = "audiocapturemode:i:1;audiomode:i:0;"
description = "${var.prefix} Terraform HostPool"
type = "Pooled"
maximum_sessions_allowed = 16
load_balancer_type = "DepthFirst" #[BreadthFirst DepthFirst]
registration_info {
expiration_date = time_rotating.avd_token.rotation_rfc3339
}
}
# Create AVD DAG
resource "azurerm_virtual_desktop_application_group" "dag" {
resource_group_name = azurerm_resource_group.rg.name
host_pool_id = azurerm_virtual_desktop_host_pool.hostpool.id
location = var.deploy_location
type = "Desktop"
name = "${var.prefix}-dag"
friendly_name = "Desktop AppGroup"
description = "AVD application group"
depends_on = [azurerm_virtual_desktop_host_pool.hostpool, azurerm_virtual_desktop_workspace.workspace]
}
# Associate Workspace and DAG
resource "azurerm_virtual_desktop_workspace_application_group_association" "ws-dag" {
application_group_id = azurerm_virtual_desktop_application_group.dag.id
workspace_id = azurerm_virtual_desktop_workspace.workspace.id
}

View File

@ -0,0 +1,69 @@
# As an alternate to Azure Files storage use this file to replace afstorage.tf to deploy NetApp Files storage for profiles in a dedicated subnet (access needs to be granted to the ANF service)
# Set up Azure NetApp Files https://docs.microsoft.com/en-us/azure/azure-netapp-files/azure-netapp-files-quickstart-set-up-account-create-volumes?tabs=azure-portal
resource "azurerm_subnet" "netapp_subnet" {
name = var.netapp_subnet_name
resource_group_name = var.rg_name
virtual_network_name = azurerm_virtual_network.vnet.name
address_prefixes = var.netapp_address
delegation {
name = "NetAppdelegation"
service_delegation {
name = "Microsoft.Netapp/volumes"
}
}
}
resource "azurerm_netapp_account" "netapp_acct" {
name = var.netapp_acct_name
resource_group_name = var.rg_name
location = var.deploy_location
active_directory {
username = var.domain_user_upn
password = var.domain_password
smb_server_name = var.netapp_smb_name
dns_servers = var.dns_servers
domain = var.domain_name
organizational_unit = var.ou_path
}
depends_on = [
azurerm_resource_group.rg
]
}
resource "azurerm_netapp_pool" "netapp_pool" {
name = var.netapp_pool_name
location = var.deploy_location
resource_group_name = var.rg_name
account_name = var.netapp_acct_name
service_level = "Standard"
size_in_tb = 4
depends_on = [
azurerm_resource_group.rg, azurerm_netapp_account.netapp_acct
]
}
resource "azurerm_netapp_volume" "NetApp_Vol" {
lifecycle {
prevent_destroy = true
}
name = var.netapp_volume_name
location = var.deploy_location
resource_group_name = var.rg_name
account_name = var.netapp_acct_name
pool_name = var.netapp_pool_name
volume_path = var.netapp_volume_path
service_level = "Standard"
subnet_id = azurerm_subnet.netapp_subnet.id
protocols = ["CIFS"]
storage_quota_in_gb = 100
depends_on = [
azurerm_netapp_pool.netapp_pool
]
}

View File

@ -0,0 +1,57 @@
resource "azurerm_virtual_network" "vnet" {
name = "${var.prefix}-VNet"
address_space = var.vnet_range
dns_servers = var.dns_servers
location = var.deploy_location
resource_group_name = var.rg_name
depends_on = [azurerm_resource_group.rg]
}
resource "azurerm_subnet" "subnet" {
name = "default"
resource_group_name = var.rg_name
virtual_network_name = azurerm_virtual_network.vnet.name
address_prefixes = var.subnet_range
depends_on = [azurerm_resource_group.rg]
}
resource "azurerm_network_security_group" "nsg" {
name = "${var.prefix}-NSG"
location = var.deploy_location
resource_group_name = var.rg_name
security_rule {
name = "HTTPS"
priority = 1001
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "443"
source_address_prefix = "*"
destination_address_prefix = "*"
}
depends_on = [azurerm_resource_group.rg]
}
resource "azurerm_subnet_network_security_group_association" "nsg_assoc" {
subnet_id = azurerm_subnet.subnet.id
network_security_group_id = azurerm_network_security_group.nsg.id
}
data "azurerm_virtual_network" "ad_vnet_data" {
name = var.ad_vnet
resource_group_name = var.ad_rg
}
resource "azurerm_virtual_network_peering" "peer1" {
name = "peer_avd_ad"
resource_group_name = var.rg_name
virtual_network_name = azurerm_virtual_network.vnet.name
remote_virtual_network_id = data.azurerm_virtual_network.ad_vnet_data.id
}
resource "azurerm_virtual_network_peering" "peer2" {
name = "peer_ad_avd"
resource_group_name = var.ad_rg
virtual_network_name = var.ad_vnet
remote_virtual_network_id = azurerm_virtual_network.vnet.id
}

View File

@ -0,0 +1,39 @@
output "resource_group_name" {
description = "Name of the Resource group created"
value = azurerm_resource_group.rg.name
}
output "location" {
description = "The Azure region"
value = azurerm_resource_group.rg.location
}
output "storage_account_share" {
description = "Name of the Azure File Share created for FSLogix"
value = azurerm_storage_share.FSShare.name
}
output "rdshcount" {
description = "The number of VMs created"
value = var.rdsh_count
}
output "dnsservers" {
description = "Custom DNS configuration"
value = azurerm_virtual_network.vnet.dns_servers
}
output "vnetrange" {
description = "Address range for deployment vnet"
value = azurerm_virtual_network.vnet.address_space
}
output "avdusers" {
description = "AVD users"
value = azuread_group.aad_group.members
}
output "aadgroupname" {
description = "Azure Active Directory Group for AVD users"
value = azuread_group.aad_group.display_name
}

View File

@ -0,0 +1,15 @@
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~>2.0"
}
azuread = {
source = "hashicorp/azuread"
}
}
}
provider "azurerm" {
features {}
}

View File

@ -0,0 +1,25 @@
data "azuread_user" "aad_user" {
for_each = toset(var.avd_users)
user_principal_name = format("%s", each.key)
}
data "azurerm_role_definition" "role" { # access an existing built-in role
name = "Desktop Virtualization User"
}
resource "azuread_group" "aad_group" {
display_name = var.aad_group_name
security_enabled = true
}
resource "azuread_group_member" "aad_group_member" {
for_each = data.azuread_user.aad_user
group_object_id = azuread_group.aad_group.id
member_object_id = each.value["id"]
}
resource "azurerm_role_assignment" "role" {
scope = azurerm_virtual_desktop_application_group.dag.id
role_definition_id = data.azurerm_role_definition.role.id
principal_id = azuread_group.aad_group.id
}

View File

@ -0,0 +1,35 @@
resource "azurerm_resource_group" "sigrg" {
location = var.deploy_location
name = "${var.prefix}-rg"
}
# Creates Shared Image Gallery
# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/shared_image_gallery
resource "azurerm_shared_image_gallery" "sig" {
name = "AVDTFsig"
resource_group_name = azurerm_resource_group.rg.name
location = azurerm_resource_group.rg.location
description = "Shared images"
tags = {
Environment = "Demo"
Tech = "Terraform"
}
}
#Creates image definition
# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/shared_image
resource "azurerm_shared_image" "example" {
name = "avd-image"
gallery_name = azurerm_shared_image_gallery.sig.name
resource_group_name = azurerm_resource_group.rg.name
location = azurerm_resource_group.rg.location
os_type = "Windows"
identifier {
publisher = "MicrosoftWindowsDesktop"
offer = "office-365"
sku = "20h2-evd-o365pp"
}
}

View File

@ -0,0 +1,147 @@
variable "rg_name" {
type = string
default = "avd-resources-rg"
description = "Name of the Resource group in which to deploy these resources"
}
variable "deploy_location" {
type = string
default = "east us"
description = "The Azure Region in which all resources in this example should be created."
}
variable "workspace" {
type = string
description = "Name of the Azure Virtual Desktop workspace"
default = "AVD TF Workspace"
}
variable "hostpool" {
type = string
description = "Name of the Azure Virtual Desktop host pool"
default = "AVD-TF-HP"
}
variable "ad_vnet" {
type = string
default = "infra-network"
description = "Name of domain controller vnet"
}
variable "dns_servers" {
type = list(string)
default = ["10.0.1.4", "168.63.129.16"]
description = "Custom DNS configuration"
}
variable "vnet_range" {
type = list(string)
default = ["10.1.0.0/16"]
description = "Address range for deployment VNet"
}
variable "subnet_range" {
type = list(string)
default = ["10.1.0.0/24"]
description = "Address range for session host subnet"
}
variable "ad_rg" {
type = string
default = "infra-rg"
description = "The resource group for AD VM"
}
variable "avd_users" {
description = "AVD users"
default = [
"avduser01@infra.local",
"avduser01@infra.local"
]
}
variable "aad_group_name" {
type = string
default = "AVDUsers"
description = "Azure Active Directory Group for AVD users"
}
variable "rdsh_count" {
description = "Number of AVD machines to deploy"
default = 2
}
variable "prefix" {
type = string
default = "avdtf"
description = "Prefix of the name of the AVD machine(s)"
}
variable "domain_name" {
type = string
default = "infra.local"
description = "Name of the domain to join"
}
variable "domain_user_upn" {
type = string
default = "admin" # do not include domain name as this is appended
description = "Username for domain join (do not include domain name as this is appended)"
}
variable "domain_password" {
type = string
default = "ChangeMe123!"
description = "Password of the user to authenticate with the domain"
sensitive = true
}
variable "vm_size" {
description = "Size of the machine to deploy"
default = "Standard_DS2_v2"
}
variable "ou_path" {
default = ""
}
variable "local_admin_username" {
type = string
default = "localadm"
description = "local admin username"
}
variable "local_admin_password" {
type = string
default = "ChangeMe123!"
description = "local admin password"
sensitive = true
}
variable "netapp_acct_name" {
default = "AVD_NetApp"
}
variable "netapp_pool_name" {
default = "AVD_NetApp_pool"
}
variable "netapp_volume_name" {
default = "AVD_NetApp_volume"
}
variable "netapp_smb_name" {
default = "AVDNetApp"
}
variable "netapp_volume_path" {
default = "AVDNetAppVolume"
}
variable "netapp_subnet_name" {
default = "NetAppSubnet"
}
variable "netapp_address" {
default = ["10.1.1.0/24"]
description = "Address range for NetApp Subnet"
}

View File

@ -0,0 +1,123 @@
## Terraform for Azure Virtual Desktop
The purpose of this repository is to demonstrate using Terraform to deploy a simple Azure Virtual Desktop environment. For Classic Azure Virtual Desktop click [here](https://github.com/Azure/RDS-Templates/tree/master/wvd-sh/terraform-azurerm-windowsvirtualdesktop).
## Requirements and limitations
* Ensure that you meet the [requirements for Azure Virtual Desktop](https://docs.microsoft.com/en-us/azure/virtual-desktop/overview#requirements)
* Terraform must be installed and configured as outlined [here](https://docs.microsoft.com/en-us/azure/developer/terraform/get-started-cloud-shell)
* Active Directory already in place in this example, we are using AD in its own VNet.
* Users in AAD that will be given access to AVD
* This demo does not support Azure ADDS only deployment
* Destroy could produce errors deleting subnet due to resources associated. Manually delete resources within the subnet before running destroy
## Components
* Azure Virtual Desktop Environment
* Networking Infrastructure
* Session Hosts
* Profile Storage
* Role Based Access Control
## Features
This directory contains the various components for building out Azure Virtual Desktop.
* `main.tf`
deploys a new workspace, hostpool, application group with associations
* `networking.tf`
deploys a new vnet, subnet, nsg and peering to AD vnet
* `host.tf`
deploys new session host from the marketplace build and join to domain
* `afstorage.tf`
deploys Azure Files storage for profiles and creates file share with RBAC permissions for the users group ([NTFS permissions will need to be configured](https://docs.microsoft.com/en-us/azure/virtual-desktop/create-file-share))
* `rbac.tf`
deploys rbac assignment for the users group
* `variables.tf`
Input variables
* `loganalytics.tf`
deploys log anaylytics workspace
* `sig.tf`
deploys log anaylytics workspace
* `random.tf`
Random provider configuration
* `defaults.tfvars`
declares the actual input values (keep security in mind if you are putting confidential data)
* `provider.tf`
Azure RM and Azure AD provider configuration
* `outputs.tf`
defines the outputs that will be displayed on deployment
## Variable Inputs
[Variable Inputs](USAGE.md#inputs)
## Deploy
If youve not previously setup terraform, check out this article to get it installed [Quickstart - Configure Terraform using Azure Cloud Shell](https://docs.microsoft.com/en-us/azure/developer/terraform/get-started-cloud-shell)
You can review our sample configuration video here
Once Terraform is setup and you have created your Terraform templates, the first step is to initialize Terraform. This step ensures that Terraform has all the prerequisites to build your template in Azure.
```
terraform init
```
The next step is to have Terraform review and validate the template. An execution plan is generated and stored in the file specified by the -out parameter.
We also need to pass our variable definitions file during the plan. We can either load it automatically by renaming env.tfvars as terraform.tfvars OR env.auto.tfvars, in which case we will use the following to create the execution plan:
```bash
terraform plan -out terraform_azure.tfplan
```
When you're ready to build the infrastructure in Azure, apply the execution plan:
```bash
terraform apply terraform_azure.tfplan
```
## Final Configuration
Youll notice we didnt actually configure the session hosts to use our profile storage at any point. There is an assumption that we are using GPO to manage FSLogix across our host pools as documented here: [Use FSLogix Group Policy Template Files - FSLogix](https://docs.microsoft.com/en-us/fslogix/use-group-policy-templates-ht).
At a minimum youll need to configure the registry keys to enable FSLogix and configure the VHD Location to the NetApp Share URI: [Profile Container registry configuration settings - FSLogix](https://docs.microsoft.com/en-us/fslogix/profile-container-configuration-reference#enabled)
## Troubleshooting Terraform deployment
<details>
<summary>Click to expand</summary>
Terraform deployment can fail in two main categories:
Issues with Terraform code
1. [Issues with Desired State Configuration (DSC)](#issues-with-desired-state-configuration-dsc)
2. [Issues with Terraform code](#issues-with-desired-state-configuration-dsc)
While it is rare to have issues with the Terraform code it is still possible, however most often errors are due to bad input in variables.tf.
* If there are errors in the Terraform code, please file a GitHub issue.
* If there are warning in the Terraform code feel free to ignore or address for your own instance of that code.
* Using Terraform error messages it's a good starting point towards identifying issues with input variables
### Issues with Desired State Configuration (DSC)
To troubleshoot this type of issue, navigate to the Azure portal and if needed reset the password on the VM that failed DSC. Once you are able to log in to the VM review the log files in the following two folders:
</details>
## Additional References
<details>
<summary>Click to expand</summary>
* [Terraform Download](https://www.terraform.io/downloads.html)
* [Visual Code Download](https://code.visualstudio.com/Download)
* [Powershell VS Code Extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode.PowerShell)
* [HashiCorp Terraform VS Code Extension](https://marketplace.visualstudio.com/items?itemName=HashiCorp.terraform)
* [Azure Terraform VS Code Extension Name](https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-azureterraform)
* [Azure CLI](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli-windows?tabs=azure-cli)
* [Configure the Azure Terraform Visual Studio Code extension](https://docs.microsoft.com/en-us/azure/developer/terraform/configure-vs-code-extension-for-terraform)
* [Setup video](https://youtu.be/YmbmpGdhI6w)
</details>

View File

@ -0,0 +1,102 @@
# Usage
<!--- BEGIN_TF_DOCS --->
## Requirements
| Name | Version |
|------|---------|
| <a name="requirement_azurerm"></a> [azurerm](#requirement\_azurerm) | ~>2.0 |
## Providers
| Name | Version |
|------|---------|
| <a name="provider_azuread"></a> [azuread](#provider\_azuread) | n/a |
| <a name="provider_azurerm"></a> [azurerm](#provider\_azurerm) | ~>2.0 |
| <a name="provider_random"></a> [random](#provider\_random) | n/a |
| <a name="provider_time"></a> [time](#provider\_time) | n/a |
## Modules
No modules.
## Resources
| Name | Type |
|------|------|
| [azuread_group.aad_group](https://registry.terraform.io/providers/hashicorp/azuread/latest/docs/resources/group) | resource |
| [azuread_group_member.aad_group_member](https://registry.terraform.io/providers/hashicorp/azuread/latest/docs/resources/group_member) | resource |
| [azurerm_log_analytics_workspace.law](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/log_analytics_workspace) | resource |
| [azurerm_network_interface.avd_vm_nic](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/network_interface) | resource |
| [azurerm_network_security_group.nsg](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/network_security_group) | resource |
| [azurerm_resource_group.log](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/resource_group) | resource |
| [azurerm_resource_group.rg](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/resource_group) | resource |
| [azurerm_resource_group.rg_storage](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/resource_group) | resource |
| [azurerm_resource_group.sigrg](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/resource_group) | resource |
| [azurerm_role_assignment.af_role](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/role_assignment) | resource |
| [azurerm_role_assignment.role](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/role_assignment) | resource |
| [azurerm_shared_image.example](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/shared_image) | resource |
| [azurerm_shared_image_gallery.sig](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/shared_image_gallery) | resource |
| [azurerm_storage_account.storage](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/storage_account) | resource |
| [azurerm_storage_share.FSShare](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/storage_share) | resource |
| [azurerm_subnet.subnet](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/subnet) | resource |
| [azurerm_subnet_network_security_group_association.nsg_assoc](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/subnet_network_security_group_association) | resource |
| [azurerm_virtual_desktop_application_group.dag](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/virtual_desktop_application_group) | resource |
| [azurerm_virtual_desktop_host_pool.hostpool](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/virtual_desktop_host_pool) | resource |
| [azurerm_virtual_desktop_workspace.workspace](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/virtual_desktop_workspace) | resource |
| [azurerm_virtual_desktop_workspace_application_group_association.ws-dag](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/virtual_desktop_workspace_application_group_association) | resource |
| [azurerm_virtual_machine_extension.domain_join](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/virtual_machine_extension) | resource |
| [azurerm_virtual_machine_extension.vmext_dsc](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/virtual_machine_extension) | resource |
| [azurerm_virtual_network.vnet](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/virtual_network) | resource |
| [azurerm_virtual_network_peering.peer1](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/virtual_network_peering) | resource |
| [azurerm_virtual_network_peering.peer2](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/virtual_network_peering) | resource |
| [azurerm_windows_virtual_machine.avd_vm](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/windows_virtual_machine) | resource |
| [random_string.AVD_local_password](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/string) | resource |
| [random_string.random](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/string) | resource |
| [time_rotating.avd_token](https://registry.terraform.io/providers/hashicorp/time/latest/docs/resources/rotating) | resource |
| [azuread_user.aad_user](https://registry.terraform.io/providers/hashicorp/azuread/latest/docs/data-sources/user) | data source |
| [azurerm_role_definition.role](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/role_definition) | data source |
| [azurerm_role_definition.storage_role](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/role_definition) | data source |
| [azurerm_virtual_network.ad_vnet_data](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/data-sources/virtual_network) | data source |
## Inputs
| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| <a name="input_aad_group_name"></a> [aad\_group\_name](#input\_aad\_group\_name) | Azure Active Directory Group for AVD users | `string` | n/a | yes |
| <a name="input_ad_rg"></a> [ad\_rg](#input\_ad\_rg) | The resource group for AD VM | `string` | n/a | yes |
| <a name="input_ad_vnet"></a> [ad\_vnet](#input\_ad\_vnet) | Name of domain controller vnet | `string` | n/a | yes |
| <a name="input_avd_users"></a> [avd\_users](#input\_avd\_users) | AVD users | `list` | `[]` | no |
| <a name="input_deploy_location"></a> [deploy\_location](#input\_deploy\_location) | The Azure Region in which all resources in this example should be created. | `string` | n/a | yes |
| <a name="input_dns_servers"></a> [dns\_servers](#input\_dns\_servers) | Custom DNS configuration | `list(string)` | n/a | yes |
| <a name="input_domain_name"></a> [domain\_name](#input\_domain\_name) | Name of the domain to join | `string` | n/a | yes |
| <a name="input_domain_password"></a> [domain\_password](#input\_domain\_password) | Password of the user to authenticate with the domain | `string` | n/a | yes |
| <a name="input_domain_user_upn"></a> [domain\_user\_upn](#input\_domain\_user\_upn) | Username for domain join (do not include domain name as this is appended) | `string` | n/a | yes |
| <a name="input_hostpool"></a> [hostpool](#input\_hostpool) | Name of the Azure Virtual Desktop host pool | `string` | `"AVD-TF-HP"` | no |
| <a name="input_local_admin_password"></a> [local\_admin\_password](#input\_local\_admin\_password) | local admin password | `any` | n/a | yes |
| <a name="input_local_admin_username"></a> [local\_admin\_username](#input\_local\_admin\_username) | local admin username | `string` | n/a | yes |
| <a name="input_ou_path"></a> [ou\_path](#input\_ou\_path) | n/a | `string` | `""` | no |
| <a name="input_prefix"></a> [prefix](#input\_prefix) | Prefix of the name of the AVD machine(s) | `string` | n/a | yes |
| <a name="input_rdsh_count"></a> [rdsh\_count](#input\_rdsh\_count) | Number of AVD machines to deploy | `number` | `2` | no |
| <a name="input_rg_name"></a> [rg\_name](#input\_rg\_name) | Name of the Resource group in which to deploy these resources | `string` | `"AVD-TF"` | no |
| <a name="input_shared"></a> [shared](#input\_shared) | Prefix of the name of the AVD machine(s) | `string` | n/a | yes |
| <a name="input_subnet_range"></a> [subnet\_range](#input\_subnet\_range) | Address range for session host subnet | `list(string)` | n/a | yes |
| <a name="input_vm_size"></a> [vm\_size](#input\_vm\_size) | Size of the machine to deploy | `string` | `"Standard_DS2_v2"` | no |
| <a name="input_vnet_range"></a> [vnet\_range](#input\_vnet\_range) | Address range for deployment VNet | `list(string)` | n/a | yes |
| <a name="input_workspace"></a> [workspace](#input\_workspace) | Name of the Azure Virtual Desktop workspace | `string` | `"AVD TF Workspace"` | no |
## Outputs
| Name | Description |
|------|-------------|
| <a name="output_aadgroupname"></a> [aadgroupname](#output\_aadgroupname) | Azure Active Directory Group for AVD users |
| <a name="output_avdusers"></a> [avdusers](#output\_avdusers) | AVD users |
| <a name="output_dnsservers"></a> [dnsservers](#output\_dnsservers) | Custom DNS configuration |
| <a name="output_location"></a> [location](#output\_location) | The Azure region |
| <a name="output_rdshcount"></a> [rdshcount](#output\_rdshcount) | The number of VMs created |
| <a name="output_resource_group_name"></a> [resource\_group\_name](#output\_resource\_group\_name) | Name of the Resource group created |
| <a name="output_storage_account_share"></a> [storage\_account\_share](#output\_storage\_account\_share) | Name of the Azure File Share created for FSLogix |
| <a name="output_vnetrange"></a> [vnetrange](#output\_vnetrange) | Address range for deployment vnet |
<!--- END_TF_DOCS --->

View File

@ -0,0 +1,43 @@
## Create a Resource Group for Storage
resource "azurerm_resource_group" "rg_storage" {
location = "east us"
name = "af-storage-rg"
}
# generate a random string (consisting of four characters)
# https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/string
resource "random_string" "random" {
length = 4
upper = false
special = false
}
## Azure Storage Accounts requires a globally unique names
## https://docs.microsoft.com/en-us/azure/storage/common/storage-account-overview
## Create a File Storage Account
resource "azurerm_storage_account" "storage" {
name = "stor${random_string.random.id}"
resource_group_name = azurerm_resource_group.rg_storage.name
location = azurerm_resource_group.rg_storage.location
account_tier = "Premium"
account_replication_type = "LRS"
account_kind = "FileStorage"
}
resource "azurerm_storage_share" "FSShare" {
name = "fslogix"
storage_account_name = azurerm_storage_account.storage.name
depends_on = [azurerm_storage_account.storage]
}
## Azure built-in roles
## https://docs.microsoft.com/en-us/azure/role-based-access-control/built-in-roles
data "azurerm_role_definition" "storage_role" {
name = "Storage File Data SMB Share Contributor"
}
resource "azurerm_role_assignment" "af_role" {
scope = azurerm_storage_account.storage.id
role_definition_id = data.azurerm_role_definition.storage_role.id
principal_id = azuread_group.aad_group.id
}

View File

@ -0,0 +1,20 @@
# Customized the sample values below for your environment and either rename to terraform.tfvars or env.auto.tfvars
deploy_location = "west europe"
rg_name = "avd-resources-rg"
prefix = "avdtf"
local_admin_username = "localadm"
local_admin_password = "ChangeMe123$"
vnet_range = ["10.1.0.0/16"]
subnet_range = ["10.1.0.0/24"]
dns_servers = ["10.0.1.4", "168.63.129.16"]
aad_group_name = "AVDUsers"
domain_name = "infra.local"
domain_user_upn = "admin" # do not include domain name as this is appended
domain_password = "ChangeMe123!"
ad_vnet = "infra-network"
ad_rg = "infra-rg"
avd_users = [
"avduser01@infra.local",
"avduser01@infra.local"
]

View File

@ -0,0 +1,126 @@
locals {
registration_token = azurerm_virtual_desktop_host_pool.hostpool.registration_info[0].token
}
resource "random_string" "AVD_local_password" {
count = var.rdsh_count
length = 16
special = true
min_special = 2
override_special = "*!@#?"
}
resource "azurerm_network_interface" "avd_vm_nic" {
count = var.rdsh_count
name = "${var.prefix}-${count.index + 1}-nic"
resource_group_name = var.rg_name
location = var.deploy_location
ip_configuration {
name = "nic${count.index + 1}_config"
subnet_id = azurerm_subnet.subnet.id
private_ip_address_allocation = "dynamic"
}
depends_on = [
azurerm_resource_group.rg
]
}
resource "azurerm_windows_virtual_machine" "avd_vm" {
count = var.rdsh_count
name = "${var.prefix}-${count.index + 1}"
resource_group_name = var.rg_name
location = var.deploy_location
size = var.vm_size
network_interface_ids = ["${azurerm_network_interface.avd_vm_nic.*.id[count.index]}"]
provision_vm_agent = true
admin_username = var.local_admin_username
admin_password = var.local_admin_password
os_disk {
name = "${lower(var.prefix)}-${count.index + 1}"
caching = "ReadWrite"
storage_account_type = "Standard_LRS"
}
source_image_reference {
publisher = "MicrosoftWindowsDesktop"
offer = "Windows-10"
sku = "20h2-evd"
version = "latest"
}
depends_on = [
azurerm_resource_group.rg,
azurerm_network_interface.avd_vm_nic
]
}
resource "azurerm_virtual_machine_extension" "domain_join" {
count = var.rdsh_count
name = "${var.prefix}-${count.index + 1}-domainJoin"
virtual_machine_id = azurerm_windows_virtual_machine.avd_vm.*.id[count.index]
publisher = "Microsoft.Compute"
type = "JsonADDomainExtension"
type_handler_version = "1.3"
auto_upgrade_minor_version = true
settings = <<SETTINGS
{
"Name": "${var.domain_name}",
"OUPath": "${var.ou_path}",
"User": "${var.domain_user_upn}@${var.domain_name}",
"Restart": "true",
"Options": "3"
}
SETTINGS
protected_settings = <<PROTECTED_SETTINGS
{
"Password": "${var.domain_password}"
}
PROTECTED_SETTINGS
lifecycle {
ignore_changes = [settings, protected_settings]
}
depends_on = [
azurerm_virtual_network_peering.peer1,
azurerm_virtual_network_peering.peer2
]
}
resource "azurerm_virtual_machine_extension" "vmext_dsc" {
count = var.rdsh_count
name = "${var.prefix}${count.index + 1}-avd_dsc"
virtual_machine_id = azurerm_windows_virtual_machine.avd_vm.*.id[count.index]
publisher = "Microsoft.Powershell"
type = "DSC"
type_handler_version = "2.73"
auto_upgrade_minor_version = true
settings = <<-SETTINGS
{
"modulesUrl": "https://wvdportalstorageblob.blob.core.windows.net/galleryartifacts/Configuration_3-10-2021.zip",
"configurationFunction": "Configuration.ps1\\AddSessionHost",
"properties": {
"HostPoolName":"${azurerm_virtual_desktop_host_pool.hostpool.name}"
}
}
SETTINGS
protected_settings = <<PROTECTED_SETTINGS
{
"properties": {
"registrationInfoToken": "${local.registration_token}"
}
}
PROTECTED_SETTINGS
depends_on = [
azurerm_virtual_machine_extension.domain_join,
azurerm_virtual_desktop_host_pool.hostpool
]
}

View File

@ -0,0 +1,14 @@
resource "azurerm_resource_group" "log" {
name = "${var.shared}-resources"
location = var.deploy_location
}
# Creates Log Anaylytics Workspace
# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/log_analytics_workspace
resource "azurerm_log_analytics_workspace" "law" {
name = "log${random_string.random.id}"
location = azurerm_resource_group.log.location
resource_group_name = azurerm_resource_group.log.name
sku = "PerGB2018"
retention_in_days = 30
}

View File

@ -0,0 +1,54 @@
# Create AVD Resource Group
resource "azurerm_resource_group" "rg" {
name = var.rg_name
location = var.deploy_location
}
# Create AVD workspace
resource "azurerm_virtual_desktop_workspace" "workspace" {
name = var.workspace
resource_group_name = azurerm_resource_group.rg.name
location = var.deploy_location
friendly_name = "${var.prefix} Workspace"
description = "${var.prefix} Workspace"
}
resource "time_rotating" "avd_token" {
rotation_days = 30
}
# Create AVD host pool
resource "azurerm_virtual_desktop_host_pool" "hostpool" {
resource_group_name = azurerm_resource_group.rg.name
location = var.deploy_location
name = var.hostpool
friendly_name = var.hostpool
validate_environment = true
custom_rdp_properties = "audiocapturemode:i:1;audiomode:i:0;"
description = "${var.prefix} Terraform HostPool"
type = "Pooled"
maximum_sessions_allowed = 16
load_balancer_type = "DepthFirst" #[BreadthFirst DepthFirst]
registration_info {
expiration_date = time_rotating.avd_token.rotation_rfc3339
}
}
# Create AVD DAG
resource "azurerm_virtual_desktop_application_group" "dag" {
resource_group_name = azurerm_resource_group.rg.name
host_pool_id = azurerm_virtual_desktop_host_pool.hostpool.id
location = var.deploy_location
type = "Desktop"
name = "${var.prefix}-dag"
friendly_name = "Desktop AppGroup"
description = "AVD application group"
depends_on = [azurerm_virtual_desktop_host_pool.hostpool, azurerm_virtual_desktop_workspace.workspace]
}
# Associate Workspace and DAG
resource "azurerm_virtual_desktop_workspace_application_group_association" "ws-dag" {
application_group_id = azurerm_virtual_desktop_application_group.dag.id
workspace_id = azurerm_virtual_desktop_workspace.workspace.id
}

View File

@ -0,0 +1,57 @@
resource "azurerm_virtual_network" "vnet" {
name = "${var.prefix}-VNet"
address_space = var.vnet_range
dns_servers = var.dns_servers
location = var.deploy_location
resource_group_name = var.rg_name
depends_on = [azurerm_resource_group.rg]
}
resource "azurerm_subnet" "subnet" {
name = "default"
resource_group_name = var.rg_name
virtual_network_name = azurerm_virtual_network.vnet.name
address_prefixes = var.subnet_range
depends_on = [azurerm_resource_group.rg]
}
resource "azurerm_network_security_group" "nsg" {
name = "${var.prefix}-NSG"
location = var.deploy_location
resource_group_name = var.rg_name
security_rule {
name = "HTTPS"
priority = 1001
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "443"
source_address_prefix = "*"
destination_address_prefix = "*"
}
depends_on = [azurerm_resource_group.rg]
}
resource "azurerm_subnet_network_security_group_association" "nsg_assoc" {
subnet_id = azurerm_subnet.subnet.id
network_security_group_id = azurerm_network_security_group.nsg.id
}
data "azurerm_virtual_network" "ad_vnet_data" {
name = var.ad_vnet
resource_group_name = var.ad_rg
}
resource "azurerm_virtual_network_peering" "peer1" {
name = "peer_avd_ad"
resource_group_name = var.rg_name
virtual_network_name = azurerm_virtual_network.vnet.name
remote_virtual_network_id = data.azurerm_virtual_network.ad_vnet_data.id
}
resource "azurerm_virtual_network_peering" "peer2" {
name = "peer_ad_avd"
resource_group_name = var.ad_rg
virtual_network_name = var.ad_vnet
remote_virtual_network_id = azurerm_virtual_network.vnet.id
}

View File

@ -0,0 +1,39 @@
output "resource_group_name" {
description = "Name of the Resource group created"
value = azurerm_resource_group.rg.name
}
output "location" {
description = "The Azure region"
value = azurerm_resource_group.rg.location
}
output "storage_account_share" {
description = "Name of the Azure File Share created for FSLogix"
value = azurerm_storage_share.FSShare.name
}
output "rdshcount" {
description = "The number of VMs created"
value = var.rdsh_count
}
output "dnsservers" {
description = "Custom DNS configuration"
value = azurerm_virtual_network.vnet.dns_servers
}
output "vnetrange" {
description = "Address range for deployment vnet"
value = azurerm_virtual_network.vnet.address_space
}
output "avdusers" {
description = "AVD users"
value = azuread_group.aad_group.members
}
output "aadgroupname" {
description = "Azure Active Directory Group for AVD users"
value = azuread_group.aad_group.display_name
}

View File

@ -0,0 +1,15 @@
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~>2.0"
}
azuread = {
source = "hashicorp/azuread"
}
}
}
provider "azurerm" {
features {}
}

View File

@ -0,0 +1,25 @@
data "azuread_user" "aad_user" {
for_each = toset(var.avd_users)
user_principal_name = format("%s", each.key)
}
data "azurerm_role_definition" "role" { # access an existing built-in role
name = "Desktop Virtualization User"
}
resource "azuread_group" "aad_group" {
display_name = var.aad_group_name
security_enabled = true
}
resource "azuread_group_member" "aad_group_member" {
for_each = data.azuread_user.aad_user
group_object_id = azuread_group.aad_group.id
member_object_id = each.value["id"]
}
resource "azurerm_role_assignment" "role" {
scope = azurerm_virtual_desktop_application_group.dag.id
role_definition_id = data.azurerm_role_definition.role.id
principal_id = azuread_group.aad_group.id
}

View File

@ -0,0 +1,35 @@
resource "azurerm_resource_group" "sigrg" {
location = var.deploy_location
name = "${var.prefix}-rg"
}
# Creates Shared Image Gallery
# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/shared_image_gallery
resource "azurerm_shared_image_gallery" "sig" {
name = "AVDTFsig"
resource_group_name = azurerm_resource_group.rg.name
location = azurerm_resource_group.rg.location
description = "Shared images"
tags = {
Environment = "Demo"
Tech = "Terraform"
}
}
#Creates image definition
# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/shared_image
resource "azurerm_shared_image" "example" {
name = "avd-image"
gallery_name = azurerm_shared_image_gallery.sig.name
resource_group_name = azurerm_resource_group.rg.name
location = azurerm_resource_group.rg.location
os_type = "Windows"
identifier {
publisher = "MicrosoftWindowsDesktop"
offer = "office-365"
sku = "20h2-evd-o365pp"
}
}

View File

@ -0,0 +1,118 @@
variable "rg_name" {
type = string
default = "avd-resources-rg"
description = "Name of the Resource group in which to deploy these resources"
}
variable "deploy_location" {
type = string
default = "east us"
description = "The Azure Region in which all resources in this example should be created."
}
variable "workspace" {
type = string
description = "Name of the Azure Virtual Desktop workspace"
default = "AVD TF Workspace"
}
variable "hostpool" {
type = string
description = "Name of the Azure Virtual Desktop host pool"
default = "AVD-TF-HP"
}
variable "ad_vnet" {
type = string
default = "infra-network"
description = "Name of domain controller vnet"
}
variable "dns_servers" {
type = list(string)
default = ["10.0.1.4", "168.63.129.16"]
description = "Custom DNS configuration"
}
variable "vnet_range" {
type = list(string)
default = ["10.1.0.0/16"]
description = "Address range for deployment VNet"
}
variable "subnet_range" {
type = list(string)
default = ["10.1.0.0/24"]
description = "Address range for session host subnet"
}
variable "ad_rg" {
type = string
default = "infra-rg"
description = "The resource group for AD VM"
}
variable "avd_users" {
description = "AVD users"
default = [
"avduser01@infra.local",
"avduser01@infra.local"
]
}
variable "aad_group_name" {
type = string
default = "AVDUsers"
description = "Azure Active Directory Group for AVD users"
}
variable "rdsh_count" {
description = "Number of AVD machines to deploy"
default = 2
}
variable "prefix" {
type = string
default = "avdtf"
description = "Prefix of the name of the AVD machine(s)"
}
variable "domain_name" {
type = string
default = "infra.local"
description = "Name of the domain to join"
}
variable "domain_user_upn" {
type = string
default = "admin" # do not include domain name as this is appended
description = "Username for domain join (do not include domain name as this is appended)"
}
variable "domain_password" {
type = string
default = "ChangeMe123!"
description = "Password of the user to authenticate with the domain"
sensitive = true
}
variable "vm_size" {
description = "Size of the machine to deploy"
default = "Standard_DS2_v2"
}
variable "ou_path" {
default = ""
}
variable "local_admin_username" {
type = string
default = "localadm"
description = "local admin username"
}
variable "local_admin_password" {
type = string
default = "ChangeMe123!"
description = "local admin password"
sensitive = true
}