Skip to content

Commit 5fa4806

Browse files
committed
Added AVD
1 parent eb1f78c commit 5fa4806

File tree

89 files changed

+21916
-1
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

89 files changed

+21916
-1
lines changed

src/bicep/add-ons/AVD/README.md

Lines changed: 0 additions & 1 deletion
This file was deleted.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2023 Jason Masten
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Azure Virtual Desktop Solution
2+
3+
[**Home**](./README.md) | [**Features**](./docs/features.md) | [**Design**](./docs/design.md) | [**Prerequisites**](./docs/prerequisites.md) | [**Troubleshooting**](./docs/troubleshooting.md)
4+
5+
This Azure Virtual Desktop (AVD) solution will deploy a fully operational [stamp](https://learn.microsoft.com/azure/architecture/patterns/deployment-stamp) in an Azure subscription adhereing to the [Zero Trust principles](https://learn.microsoft.com/security/zero-trust/azure-infrastructure-avd). Many of the [common features](./docs/features.md) used with AVD have been automated in this solution for your convenience. Be sure to complete the necessary [prerequisites](./docs/prerequisites.md) and to review the parameter descriptions to the understand the consequences of your selections.
6+
7+
## Deployment Options
8+
9+
> [!WARNING]
10+
> Failure to complete the [prerequisites](./docs/prerequisites.md) will result in an unsuccessful deployment.
11+
12+
### Azure Portal
13+
14+
[![Deploy to Azure](https://aka.ms/deploytoazurebutton)](https://portal.azure.com/#blade/Microsoft_Azure_CreateUIDef/CustomDeploymentBlade/uri/https%3A%2F%2Fraw.githubusercontent.com%2Fjamasten%2FAzureVirtualDesktop%2Fmain%2Fsolution.json/uiFormDefinitionUri/https%3A%2F%2Fraw.githubusercontent.com%2Fjamasten%2FAzureVirtualDesktop%2Fmain%2FuiDefinition.json)
15+
[![Deploy to Azure Gov](https://aka.ms/deploytoazuregovbutton)](https://portal.azure.us/#blade/Microsoft_Azure_CreateUIDef/CustomDeploymentBlade/uri/https%3A%2F%2Fraw.githubusercontent.com%2Fjamasten%2FAzureVirtualDesktop%2Fmain%2Fsolution.json/uiFormDefinitionUri/https%3A%2F%2Fraw.githubusercontent.com%2Fjamasten%2FAzureVirtualDesktop%2Fmain%2FuiDefinition.json)
16+
17+
### PowerShell
18+
19+
````powershell
20+
New-AzDeployment `
21+
-Location '<Azure location>' `
22+
-TemplateFile 'https://raw.githubusercontent.com/jamasten/AzureVirtualDesktop/main/solution.json' `
23+
-Verbose
24+
````
25+
26+
### Azure CLI
27+
28+
````cli
29+
az deployment sub create \
30+
--location '<Azure location>' \
31+
--template-uri 'https://raw.githubusercontent.com/jamasten/AzureVirtualDesktop/main/solution.json'
32+
````
Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
[Cmdletbinding()]
2+
Param(
3+
[parameter(Mandatory)]
4+
[string]
5+
$ActiveDirectorySolution,
6+
7+
[parameter(Mandatory)]
8+
[int]
9+
$CpuCountMax,
10+
11+
[parameter(Mandatory)]
12+
[int]
13+
$CpuCountMin,
14+
15+
[parameter(Mandatory)]
16+
[string]
17+
$DomainName,
18+
19+
[parameter(Mandatory)]
20+
[string]
21+
$Environment,
22+
23+
[parameter(Mandatory)]
24+
[string]
25+
$ImageDefinitionResourceId,
26+
27+
[parameter(Mandatory)]
28+
[string]
29+
$Location,
30+
31+
[parameter(Mandatory)]
32+
[int]
33+
$SessionHostCount,
34+
35+
[parameter(Mandatory)]
36+
[string]
37+
$StorageService,
38+
39+
[parameter(Mandatory)]
40+
[string]
41+
$SubscriptionId,
42+
43+
[parameter(Mandatory)]
44+
[string]
45+
$TenantId,
46+
47+
[parameter(Mandatory)]
48+
[string]
49+
$UserAssignedIdentityClientId,
50+
51+
[parameter(Mandatory)]
52+
[string]
53+
$VirtualMachineSize,
54+
55+
[parameter(Mandatory)]
56+
[string]
57+
$VirtualNetworkName,
58+
59+
[parameter(Mandatory)]
60+
[string]
61+
$VirtualNetworkResourceGroupName,
62+
63+
[parameter(Mandatory)]
64+
[string]
65+
$WorkspaceNamePrefix,
66+
67+
[parameter(Mandatory)]
68+
[string]
69+
$WorkspaceResourceGroupName
70+
)
71+
72+
function Write-Log
73+
{
74+
param(
75+
[parameter(Mandatory)]
76+
[string]$Message,
77+
78+
[parameter(Mandatory)]
79+
[string]$Type
80+
)
81+
$Path = 'C:\cse.txt'
82+
if(!(Test-Path -Path $Path))
83+
{
84+
New-Item -Path 'C:\' -Name 'cse.txt' | Out-Null
85+
}
86+
$Timestamp = Get-Date -Format 'MM/dd/yyyy HH:mm:ss.ff'
87+
$Entry = '[' + $Timestamp + '] [' + $Type + '] ' + $Message
88+
$Entry | Out-File -FilePath $Path -Append
89+
}
90+
91+
$ErrorActionPreference = 'Stop'
92+
$WarningPreference = 'SilentlyContinue'
93+
94+
try
95+
{
96+
Connect-AzAccount -Environment $Environment -Tenant $TenantId -Subscription $SubscriptionId -Identity -AccountId $UserAssignedIdentityClientId | Out-Null
97+
$Sku = Get-AzComputeResourceSku -Location $Location | Where-Object {$_.ResourceType -eq "virtualMachines" -and $_.Name -eq $VirtualMachineSize}
98+
99+
##############################################################
100+
# Accelerated Networking Validation
101+
##############################################################
102+
$AcceleratedNetworking = ($Sku.capabilities | Where-Object {$_.name -eq "AcceleratedNetworkingEnabled"}).value
103+
Write-Log -Message "Accelerated Networking Validation Succeeded" -Type 'INFO'
104+
105+
##############################################################
106+
# Availability Zone Validation
107+
##############################################################
108+
$AvailabilityZones = $Sku.locationInfo.zones | Sort-Object
109+
Write-Log -Message "Availability Zone Validation Succeeded" -Type 'INFO'
110+
111+
##############################################################
112+
# Azure NetApp Files Validation
113+
##############################################################
114+
if($StorageService -eq 'AzureNetAppFiles')
115+
{
116+
$Vnet = Get-AzVirtualNetwork -Name $VirtualNetworkName -ResourceGroupName $VirtualNetworkResourceGroupName
117+
$DnsServers = $Vnet.DhcpOptions.DnsServers -join ','
118+
$SubnetId = ($Vnet.Subnets | Where-Object {$_.Delegations[0].ServiceName -eq "Microsoft.NetApp/volumes"}).Id
119+
if($null -eq $SubnetId -or $SubnetId -eq "")
120+
{
121+
Write-Error -Exception "INVALID AZURE NETAPP FILES CONFIGURATION: A dedicated subnet must be delegated to the ANF resource provider."
122+
}
123+
$DeployAnfAd = "true"
124+
$Accounts = Get-AzResource -ResourceType "Microsoft.NetApp/netAppAccounts" | Where-Object {$_.Location -eq $Location}
125+
if($Accounts.Count -gt 0)
126+
{
127+
$AnfAdCounter = 0
128+
foreach($Account in $Accounts)
129+
{
130+
$Params = @{
131+
ResourceGroupName = $Account.ResourceGroupName
132+
ResourceProviderName = 'Microsoft.NetApp'
133+
ResourceType = 'netAppAccounts'
134+
Name = $Account.Name
135+
ApiVersion = '2023-07-01'
136+
Method = 'GET'
137+
}
138+
$AD = ((Invoke-AzRestMethod @Params).Content | ConvertFrom-Json).properties.activeDirectories.activeDirectoryId
139+
if($AD)
140+
{
141+
$AnfAdCounter++
142+
}
143+
}
144+
if($AnfAdCounter -gt 0)
145+
{
146+
$DeployAnfAd = "false"
147+
}
148+
}
149+
Write-Log -Message "Azure NetApp Files Validation Succeeded" -Type 'INFO'
150+
}
151+
152+
##############################################################
153+
# Disk SKU Validation
154+
##############################################################
155+
if(($Sku.capabilities | Where-Object {$_.name -eq "PremiumIO"}).value -eq $false)
156+
{
157+
Write-Error -Exception "INVALID DISK SKU: The selected VM Size does not support the Premium SKU for managed disks."
158+
}
159+
Write-Log -Message "Disk SKU Validation Succeeded" -Type 'INFO'
160+
161+
##############################################################
162+
# Hyper-V Generation Validation
163+
##############################################################
164+
if(($Sku.capabilities | Where-Object {$_.name -eq "HyperVGenerations"}).value -notlike "*2")
165+
{
166+
Write-Error -Exception "INVALID HYPER-V GENERATION: The selected VM size does not support the selected Image Sku."
167+
}
168+
Write-Log -Message "Hyper-V Generation Validation Succeeded" -Type 'INFO'
169+
170+
##############################################################
171+
# Kerberos Encryption Validation
172+
##############################################################
173+
if($ActiveDirectorySolution -eq 'MicrosoftEntraDomainServices')
174+
{
175+
$KerberosRc4Encryption = (Get-AzResource -Name $DomainName -ExpandProperties).Properties.domainSecuritySettings.kerberosRc4Encryption
176+
if($KerberosRc4Encryption -eq "Enabled")
177+
{
178+
Write-Error -Exception "INVALID KERBEROS ENCRYPTION: The Kerberos Encryption on Azure AD DS does not match your Kerberos Encyrption selection."
179+
}
180+
Write-Log -Message "Kerberos Encryption Validation Succeeded" -Type 'INFO'
181+
}
182+
183+
##############################################################
184+
# Trusted Launch Validation
185+
##############################################################
186+
# Validates the VM Size does not have Trusted Launch disabled and has Hyper-V Generation enabled
187+
# https://learn.microsoft.com/azure/virtual-machines/trusted-launch-faq?tabs=PowerShell#how-can-i-find-vm-sizes-that-support-trusted-launch
188+
$TrustedLaunchDisabled = $Sku.Capabilities | Where-Object {$_.Name -eq "TrustedLaunchDisabled"} | Select-Object -ExpandProperty Value
189+
$HyperVGeneration = $Sku.Capabilities | Where-Object {$_.Name -eq "HyperVGenerations"} | Select-Object -ExpandProperty Value
190+
191+
if($TrustedLaunchDisabled -or $HyperVGeneration -eq "V1")
192+
{
193+
Write-Error -Exception "INVALID TRUSTED LAUNCH: The selected VM Size does not support Trusted Launch."
194+
}
195+
196+
# Validates the custom image if applicable
197+
# https://learn.microsoft.com/en-us/azure/virtual-machines/trusted-launch-faq?tabs=PowerShell#how-can-i-validate-if-os-image-supports-trusted-launch
198+
if($ImageDefinitionResourceId -ne 'NotApplicable')
199+
{
200+
$ImageDefinition = Get-AzGalleryImageDefinition -ResourceId $ImageDefinitionResourceId
201+
$SecurityType = ($ImageDefinition.Features | Where-Object {$_.Name -eq 'SecurityType'}).Value
202+
$HyperVGeneration = $ImageDefinition.HyperVGeneration
203+
if($SecurityType -notlike "*TrustedLaunch*" -or $HyperVGeneration -notlike "*V2*")
204+
{
205+
Write-Error -Exception "INVALID TRUSTED LAUNCH: The selected Image Definition does not support Trusted Launch."
206+
}
207+
}
208+
Write-Log -Message "Trusted Launch Validation Succeeded" -Type 'INFO'
209+
210+
211+
##############################################################
212+
# vCPU Count Validation
213+
##############################################################
214+
# Recommended minimum vCPU is 4 for multisession hosts and 2 for single session hosts.
215+
# Recommended maximum vCPU is 32 for multisession hosts and 128 for single session hosts.
216+
# https://learn.microsoft.com/windows-server/remote/remote-desktop-services/virtual-machine-recs
217+
$vCPUs = [int]($Sku.capabilities | Where-Object {$_.name -eq "vCPUs"}).value
218+
if($vCPUs -lt $CpuCountMin -or $vCPUs -gt $CpuCountMax)
219+
{
220+
Write-Error -Exception "INVALID VCPU COUNT: The selected VM Size does not contain the appropriate amount of vCPUs for Azure Virtual Desktop. https://learn.microsoft.com/windows-server/remote/remote-desktop-services/virtual-machine-recs"
221+
}
222+
Write-Log -Message "vCPU Count Validation Succeeded" -Type 'INFO'
223+
224+
##############################################################
225+
# vCPU Quota Validation
226+
##############################################################
227+
$RequestedCores = $vCPUs * $SessionHostCount
228+
$Family = (Get-AzComputeResourceSku -Location $Location | Where-Object {$_.Name -eq $VirtualMachineSize}).Family
229+
$CpuData = Get-AzVMUsage -Location $Location | Where-Object {$_.Name.Value -eq $Family}
230+
$AvailableCores = $CpuData.Limit - $CpuData.CurrentValue; $RequestedCores = $vCPUs * $SessionHostCount
231+
if($RequestedCores -gt $AvailableCores)
232+
{
233+
Write-Error -Exception "INSUFFICIENT CORE QUOTA: The selected VM size, $VirtualMachineSize, does not have adequate core quota in the selected location."
234+
}
235+
Write-Log -Message "vCPU Quota Validation Succeeded" -Type 'INFO'
236+
237+
##############################################################
238+
# AVD Workspace Validation
239+
##############################################################
240+
$Workspace = Get-AzResource -ResourceGroupName $WorkspaceResourceGroupName -ResourceName $($WorkspaceNamePrefix + '-feed')
241+
Write-Log -Message "Existing Workspace Validation Succeeded" -Type 'INFO'
242+
243+
Disconnect-AzAccount | Out-Null
244+
245+
$Output = [pscustomobject][ordered]@{
246+
acceleratedNetworking = $AcceleratedNetworking.ToLower()
247+
anfDnsServers = if($StorageService -eq "AzureNetAppFiles"){$DnsServers}else{"NotApplicable"}
248+
anfSubnetId = if($StorageService -eq "AzureNetAppFiles"){$SubnetId}else{"NotApplicable"}
249+
anfActiveDirectory = if($StorageService -eq "AzureNetAppFiles"){$DeployAnfAd}else{"false"}
250+
availabilityZones = $AvailabilityZones
251+
existingWorkspace = if($Workspace){"true"}else{"false"}
252+
}
253+
$JsonOutput = $Output | ConvertTo-Json
254+
return $JsonOutput
255+
}
256+
catch
257+
{
258+
Write-Log -Message $_ -Type 'ERROR'
259+
throw
260+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
[Cmdletbinding()]
2+
Param(
3+
[parameter(Mandatory)]
4+
[string]
5+
$Installer
6+
)
7+
8+
function Write-Log
9+
{
10+
param(
11+
[parameter(Mandatory)]
12+
[string]$Message,
13+
14+
[parameter(Mandatory)]
15+
[string]$Type
16+
)
17+
$Path = 'C:\cse.txt'
18+
if(!(Test-Path -Path $Path))
19+
{
20+
New-Item -Path 'C:\' -Name 'cse.txt' | Out-Null
21+
}
22+
$Timestamp = Get-Date -Format 'MM/dd/yyyy HH:mm:ss.ff'
23+
$Entry = '[' + $Timestamp + '] [' + $Type + '] ' + $Message
24+
$Entry | Out-File -FilePath $Path -Append
25+
}
26+
27+
$ErrorActionPreference = 'Stop'
28+
$WarningPreference = 'SilentlyContinue'
29+
30+
try
31+
{
32+
Start-Process -FilePath 'msiexec.exe' -ArgumentList "/i $Installer /quiet /qn /norestart /passive" -Wait -Passthru | Out-Null
33+
Write-Log -Message 'Installed Azure PowerShell AZ Module' -Type 'INFO'
34+
$Output = [pscustomobject][ordered]@{
35+
installer = $Installer
36+
}
37+
$Output | ConvertTo-Json
38+
}
39+
catch
40+
{
41+
Write-Log -Message $_ -Type 'ERROR'
42+
throw
43+
}

0 commit comments

Comments
 (0)