Khoi Vo
Khoi Vo

Reputation: 1

Mounting Azure File Storage to Azure Virtual Destop Session Host using Terraform and PowerShell Script

I want to automatically mount an Azure File Share to Azure Virtual Desktop using Terraform and PowerShell Script at the provisioning time. Currently, I am using the CustomExtention PowerShell Script to mount the Azure File Share. However, I cannot achieve the goal, the UNC Path of the Azure File Share is not mounted automatically. I must access the Session Host and mount it by cmd or PowerShell. Below is my code.

Terraform Code:

locals {
  registration_token = azurerm_virtual_desktop_host_pool_registration_info.registrationinfo.token
  shutdown_command     = "shutdown -r -t 10"
  exit_code_hack       = "exit 0"
  commandtorun         = "New-Item -Path HKLM:/SOFTWARE/Microsoft/RDInfraAgent/AADJPrivate"
  powershell_command   = "${local.commandtorun}; ${local.shutdown_command}; ${local.exit_code_hack}"
}

resource "azurerm_resource_group" "rg-avd" {
    name = var.resource_group_name
    location = var.location 
}

# Create AVD workspace
resource "azurerm_virtual_desktop_workspace" "workspace" {
  name                = "AVD-${var.prefix}-Workspace"
  resource_group_name = azurerm_resource_group.rg-avd.name
  location            = azurerm_resource_group.rg-avd.location
  friendly_name       = "AVD ${var.prefix} Workspace"
  description         = "AVD ${var.prefix} Workspace"
  depends_on          = [azurerm_virtual_desktop_host_pool.hostpool, azurerm_virtual_desktop_application_group.desktop_application_group]
}
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-avd.name
  location                 = azurerm_resource_group.rg-avd.location
  name                     = "${var.prefix}${var.hostpool_name}"
  friendly_name            = "${var.prefix} Host Pool"
  validate_environment     = true
  custom_rdp_properties    = "targetisaadjoined:i:1;drivestoredirect:s:*;audiomode:i:0;videoplaybackmode:i:1;redirectclipboard:i:1;redirectprinters:i:1;devicestoredirect:s:*;redirectcomports:i:1;redirectsmartcards:i:1;usbdevicestoredirect:s:*;enablecredsspsupport:i:1;redirectwebauthn:i:1;use multimon:i:1;enablerdsaadauth:i:1"
  description              = "${var.prefix} Terraform HostPool"
  type                     = "Pooled"
  maximum_sessions_allowed = 10
  load_balancer_type       = "BreadthFirst" #[BreadthFirst DepthFirst]
  depends_on = [
    azurerm_resource_group.rg-avd
  ]
}
resource "azurerm_virtual_desktop_host_pool_registration_info" "registrationinfo" {
  hostpool_id     = azurerm_virtual_desktop_host_pool.hostpool.id
  expiration_date = time_rotating.avd_token.rotation_rfc3339
  depends_on = [
    azurerm_virtual_desktop_host_pool.hostpool
  ]
}

# Create AVD DAG
resource "azurerm_virtual_desktop_application_group" "desktop_application_group" {
  resource_group_name = azurerm_resource_group.rg-avd.name
  host_pool_id        = azurerm_virtual_desktop_host_pool.hostpool.id
  location            = azurerm_resource_group.rg-avd.location
  type                = "Desktop"
  name                = "${var.prefix}-desktop-application-group"
  friendly_name       = "${var.prefix} Desktop AppGroup"
  description         = "AVD ${var.prefix} Application Group"
  depends_on          = [azurerm_virtual_desktop_host_pool.hostpool, azurerm_virtual_machine_extension.network_file_share]

}

# Associate Workspace and DAG
resource "azurerm_virtual_desktop_workspace_application_group_association" "workspace-desktop_application_group" {
  application_group_id = azurerm_virtual_desktop_application_group.desktop_application_group.id
  workspace_id         = azurerm_virtual_desktop_workspace.workspace.id
}
resource "azurerm_network_security_group" "nsg" {
  name                = "avd_rdp_nsg"
  location            = azurerm_resource_group.rg-avd.location
  resource_group_name = azurerm_resource_group.rg-avd.name

  security_rule {
    name                       = "allow_rdp_sg"
    priority                   = 900
    direction                  = "Inbound"
    access                     = "Allow"
    protocol                   = "Tcp"
    source_port_range          = "*"
    destination_port_range     = "3389"
    source_address_prefix      = "*"
    destination_address_prefix = "*"
  }
  security_rule {
    name                       = "allow_azure_file"
    priority                   = 901
    direction                  = "Inbound"
    access                     = "Allow"
    protocol                   = "Tcp"
    source_port_range          = "*"
    destination_port_range     = "445"
    source_address_prefix      = "*"
    destination_address_prefix = "*"
  }
  security_rule {
    name                       = "allow_https"
    priority                   = 300
    direction                  = "Outbound"
    access                     = "Allow"
    protocol                   = "Tcp"
    source_port_range          = "*"
    destination_port_range     = "443"
    source_address_prefix      = "*"
    destination_address_prefix = "*"
  }
  security_rule {
    name                       = "allow_http"
    priority                   = 301
    direction                  = "Outbound"
    access                     = "Allow"
    protocol                   = "Tcp"
    source_port_range          = "*"
    destination_port_range     = "80"
    source_address_prefix      = "*"
    destination_address_prefix = "*"
  }
}
# Create Network Interface for virtual machines
resource "azurerm_network_interface" "avd_vm_nic" {
  count               = var.rdsh_count
  name                = "AVD-${var.prefix}-${count.index + 1}-nic"
  resource_group_name = azurerm_resource_group.rg-avd.name
  location            = azurerm_resource_group.rg-avd.location

  ip_configuration {
    name                          = "nic${count.index + 1}_config"
    subnet_id                     = var.subnet_id
    private_ip_address_allocation = "Dynamic"
  }

  depends_on = [
    azurerm_resource_group.rg-avd,
    azurerm_virtual_desktop_host_pool_registration_info.registrationinfo
  ]
}
resource "azurerm_network_interface_security_group_association" "association" {
  count = var.rdsh_count
  depends_on = [ azurerm_network_security_group.nsg, azurerm_network_interface.avd_vm_nic ]
  network_interface_id      = azurerm_network_interface.avd_vm_nic.*.id[count.index]
  network_security_group_id = azurerm_network_security_group.nsg.id
}
# Create Virtual Machines for AVD
resource "azurerm_windows_virtual_machine" "avd_session_host" {
  count                 = var.rdsh_count
  name                  = "AVD-${var.prefix}-${count.index + 1}"
  resource_group_name   = azurerm_resource_group.rg-avd.name
  location              = azurerm_resource_group.rg-avd.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                 = "avd-${lower(var.prefix)}-${count.index + 1}"
    caching              = "ReadWrite"
    storage_account_type = "Standard_LRS"
  }

  source_image_reference {
    publisher = "MicrosoftWindowsDesktop"
    offer     = "Windows-11"
    sku       = "win11-22h2-avd"
    version   = "latest"
  }
  identity {
    type = "SystemAssigned"
  }
  depends_on = [
    azurerm_resource_group.rg-avd,
    azurerm_network_interface.avd_vm_nic
  ]
}

# Register VM with Azure Virtual Desktop
resource "azurerm_virtual_machine_extension" "vm_avd_associate" {
  depends_on = [
      azurerm_windows_virtual_machine.avd_session_host
  ]
  count = var.rdsh_count
  name                 = "Microsoft.PowerShell.DSC"
  virtual_machine_id   = azurerm_windows_virtual_machine.avd_session_host.*.id[count.index]
  publisher            = "Microsoft.Powershell"
  type                 = "DSC"
  type_handler_version = "2.73"
  settings = <<-SETTINGS
    {
        "modulesUrl": "https://wvdportalstorageblob.blob.core.windows.net/galleryartifacts/Configuration_01-19-2023.zip",
        "ConfigurationFunction": "Configuration.ps1\\AddSessionHost",
        "Properties" : {
          "hostPoolName" : "${azurerm_virtual_desktop_host_pool.hostpool.name}",
          "aadJoin": true
        }
    }
SETTINGS

  protected_settings = <<PROTECTED_SETTINGS
  {
    "properties": {
      "registrationInfoToken": "${local.registration_token}"
    }
  }
PROTECTED_SETTINGS
}
#Join VM to domain
resource "azurerm_virtual_machine_extension" "AADLoginForWindows" {
  depends_on = [
      azurerm_windows_virtual_machine.avd_session_host,
      azurerm_virtual_machine_extension.vm_avd_associate
  ]
  count = var.rdsh_count
  name                 = "AADLoginForWindows"
  virtual_machine_id   = azurerm_windows_virtual_machine.avd_session_host.*.id[count.index]
  publisher            = "Microsoft.Azure.ActiveDirectory"
  type                 = "AADLoginForWindows"
  type_handler_version = "1.0"
  auto_upgrade_minor_version = true
}
/* resource "azurerm_virtual_machine_extension" "addaadjprivate" {
    depends_on = [
      azurerm_virtual_machine_extension.AADLoginForWindows
    ]
  count = var.rdsh_count
  name                 = "AADJPRIVATE"
  virtual_machine_id   = azurerm_windows_virtual_machine.avd_session_host.*.id[count.index]
  publisher            = "Microsoft.Compute"
  type                 = "CustomScriptExtension"
  type_handler_version = "1.10"

  settings = <<SETTINGS
    {
        "commandToExecute": "powershell.exe -Command \"${local.powershell_command}\""
    }
SETTINGS
} */

resource "azurerm_virtual_machine_extension" "network_file_share" {
    depends_on = [
      azurerm_virtual_machine_extension.AADLoginForWindows ]
  count = var.rdsh_count
  name                 = "enable-network-discovery"
  virtual_machine_id   = azurerm_windows_virtual_machine.avd_session_host.*.id[count.index]
  publisher            = "Microsoft.Compute"
  type                 = "CustomScriptExtension"
  type_handler_version = "1.10"
  settings = <<SETTINGS
    {
    "commandToExecute": "powershell -encodedCommand ${textencodebase64(file(var.network_fileshare_config), "UTF-16LE")}" 
    }
    SETTINGS
}

PowerShellScript:

# Set Network Discovery rule
Set-NetFirewallRule -DisplayGroup "Network Discovery" -Enabled True
Get-NetFirewallRule -DisplayGroup 'Network Discovery' | Set-NetFirewallRule -Profile 'Public' -Enabled true
Enable-NetFirewallRule -DisplayGroup "File and Printer Sharing"
Invoke-Expression -Command "net use `"\\avddevfileshare.file.core.windows.net\tm-prog`" /user:`"localhost\avddevfileshare`" `"password`" /persistent:`"yes`" /global"
New-Item -ItemType Directory -Path "C:\tm_prog" | Out-Null
Copy-Item -Path "\\avddevfileshare.file.core.windows.net\tm-prog\*" -Destination "C:\tm_prog" -Recurse -Force
# Create Shortcut
$batchScriptContent = @"
@echo off
net use `"\\avddevfileshare.file.core.windows.net\tm-data`" /user:`"localhost\avddevfileshare`" `"password`" /persistent:`"yes`" /global
echo Set oWS = WScript.CreateObject("WScript.Shell") > CreateShortcut.vbs
echo sLinkFile = "%ALLUSERSPROFILE%\Desktop\tangoMediaCG.lnk" >> CreateShortcut.vbs
echo Set oLink = oWS.CreateShortcut(sLinkFile) >> CreateShortcut.vbs
echo oLink.TargetPath = "C:\tm_prog\prog\win\tangoMediaCG.exe" >> CreateShortcut.vbs
echo oLink.Save >> CreateShortcut.vbs
cscript CreateShortcut.vbs 
del CreateShortcut.vbs
"@
# Define the path for the batch script
$batchScriptPath = "C:\ProgramData\Microsoft\Windows\Start Menu\Programs\StartUp\CreateShortcut.bat"
$batchScriptContent | Out-File -FilePath $batchScriptPath -Encoding ASCII
Start-Process -FilePath $batchScriptPath -Verb RunAs -PassThru
$gpPath = "HKLM:\Software\Microsoft\Windows\CurrentVersion\Policies\System"
# Set the script execution policy
Set-ItemProperty -Path $gpPath -Name "EnableScriptBlockLogging" -Value 1
Set-ItemProperty -Path $gpPath -Name "EnableScripts" -Value 1
Set-ExecutionPolicy -Scope LocalMachine -ExecutionPolicy Unrestricted -Force
# Register the script to run at startup
$taskAction = New-ScheduledTaskAction -Execute "cmd.exe" -Argument "/c `"$batchScriptPath`""
$taskTrigger = New-ScheduledTaskTrigger -AtLogon
Register-ScheduledTask -Action $taskAction -Trigger $taskTrigger -TaskName "CreateShortcutTask" -User "LocalAdmin" -Password "password" -Force -RunLevel Highest -Force
New-Item -Path HKLM:/SOFTWARE/Microsoft/RDInfraAgent/AADJPrivate; shutdown -r -t 10; exit 0

I tried to edit the PowerShell Script but looks like it does not work. I expect to have the persistent unassigned drive letter mounted UNC Path of the Azure File Share globally that anyone can access it without prompting the credentials.

Upvotes: 0

Views: 457

Answers (1)

Venkat V
Venkat V

Reputation: 7820

Mounting Azure File Storage to Azure Virtual Desktop Session Host using Terraform and PowerShell Script.

To mount Azure File Storage to Azure Virtual Desktop, you can use the following Terraform code to mount the Azure File share

I created a .ps1 file named 'fileshare.ps1' in the same directory to run the PowerShell script in the Terraform code, as shown in the screenshot below.

enter image description here

Here is the updated Terraform code.

    resource "azurerm_windows_virtual_machine" "avd_vm" {
      name                  = "demo-avd"
      resource_group_name   = data.azurerm_resource_group.example.name
      location              = data.azurerm_resource_group.example.location
      size                  = "Standard_DS2_v2"
     network_interface_ids  =  [azurerm_network_interface.nic.id]
      provision_vm_agent    = true
      admin_username        = "Venkat"
      admin_password        = "Welcome@123$"
    
      os_disk {
        name                 = "avd-os"
        caching              = "ReadWrite"
        storage_account_type = "Standard_LRS"
      }
    
      source_image_reference {
        publisher = "MicrosoftWindowsDesktop"
        offer     = "Windows-10"
        sku       = "20h2-evd"
        version   = "latest"
      }
    }
    resource "azurerm_virtual_machine_extension" "example" {
      name                 = "sample-hostname"
      virtual_machine_id   = azurerm_windows_virtual_machine.avd_vm.id
      publisher            = "Microsoft.Powershell"
      type                 = "DSC"
      type_handler_version = "2.73"
    
    settings = <<SETTINGS
        {   
        "commandToExecute": "powershell fileshare.ps1"
        }
    SETTINGS
    
      tags = {
        environment = "Production"
      }
    }

Terraform apply

After running the above terraform code, Extension has been provisioned successfully.

enter image description here

Reference: https://learn.microsoft.com/en-us/azure/developer/terraform/configure-azure-virtual-desktop

Upvotes: 0

Related Questions