From 776985c54871b63b85947f56b6cec2fadfc9fd76 Mon Sep 17 00:00:00 2001 From: brwilkinson Date: Sat, 18 May 2024 13:21:05 -0700 Subject: [PATCH] update from WVD to AVD --- .../CustomResources => 0-archive}/WVDDSC.zip | Bin .../WVDDSC/LICENSE | 0 .../WVDDSC/README.md | 0 .../WVDDSC/WVDDSC.psd1 | Bin .../WVDDSC/WVDDSC.psm1 | 0 ADF/1-prereqs/CustomResources/AVDDSC.zip | Bin 0 -> 4822 bytes .../CustomResources/AVDDSC/AVDDSC.psd1 | Bin 0 -> 1906 bytes .../CustomResources/AVDDSC/AVDDSC.psm1 | 148 ++++++++ ADF/1-prereqs/CustomResources/AVDDSC/LICENSE | 21 ++ .../CustomResources/AVDDSC/README.md | 95 +++++ ADF/bicep/00-ALL-MG.bicep | 3 + ADF/bicep/00-ALL-SUB.bicep | 3 + ADF/bicep/01-ALL-RG.bicep | 91 ++++- ADF/bicep/AVDAppGroup-AG-App.bicep | 51 +++ ADF/bicep/AVDAppGroup-AG.bicep | 88 +++++ ADF/bicep/AVDAppGroup.bicep | 95 +++++ ADF/bicep/AVDHostPool-HP.bicep | 92 +++++ ADF/bicep/AVDHostPool.bicep | 95 +++++ ADF/bicep/AVDWorkspace-WS.bicep | 54 +++ ADF/bicep/AVDWorkspace.bicep | 95 +++++ ADF/bicep/ImageBuilderAVD.bicep | 337 ++++++++++++++++++ 21 files changed, 1251 insertions(+), 17 deletions(-) rename ADF/{1-prereqs/CustomResources => 0-archive}/WVDDSC.zip (100%) rename ADF/{1-prereqs/CustomResources => 0-archive}/WVDDSC/LICENSE (100%) rename ADF/{1-prereqs/CustomResources => 0-archive}/WVDDSC/README.md (100%) rename ADF/{1-prereqs/CustomResources => 0-archive}/WVDDSC/WVDDSC.psd1 (100%) rename ADF/{1-prereqs/CustomResources => 0-archive}/WVDDSC/WVDDSC.psm1 (100%) create mode 100644 ADF/1-prereqs/CustomResources/AVDDSC.zip create mode 100644 ADF/1-prereqs/CustomResources/AVDDSC/AVDDSC.psd1 create mode 100644 ADF/1-prereqs/CustomResources/AVDDSC/AVDDSC.psm1 create mode 100644 ADF/1-prereqs/CustomResources/AVDDSC/LICENSE create mode 100644 ADF/1-prereqs/CustomResources/AVDDSC/README.md create mode 100644 ADF/bicep/AVDAppGroup-AG-App.bicep create mode 100644 ADF/bicep/AVDAppGroup-AG.bicep create mode 100644 ADF/bicep/AVDAppGroup.bicep create mode 100644 ADF/bicep/AVDHostPool-HP.bicep create mode 100644 ADF/bicep/AVDHostPool.bicep create mode 100644 ADF/bicep/AVDWorkspace-WS.bicep create mode 100644 ADF/bicep/AVDWorkspace.bicep create mode 100644 ADF/bicep/ImageBuilderAVD.bicep diff --git a/ADF/1-prereqs/CustomResources/WVDDSC.zip b/ADF/0-archive/WVDDSC.zip similarity index 100% rename from ADF/1-prereqs/CustomResources/WVDDSC.zip rename to ADF/0-archive/WVDDSC.zip diff --git a/ADF/1-prereqs/CustomResources/WVDDSC/LICENSE b/ADF/0-archive/WVDDSC/LICENSE similarity index 100% rename from ADF/1-prereqs/CustomResources/WVDDSC/LICENSE rename to ADF/0-archive/WVDDSC/LICENSE diff --git a/ADF/1-prereqs/CustomResources/WVDDSC/README.md b/ADF/0-archive/WVDDSC/README.md similarity index 100% rename from ADF/1-prereqs/CustomResources/WVDDSC/README.md rename to ADF/0-archive/WVDDSC/README.md diff --git a/ADF/1-prereqs/CustomResources/WVDDSC/WVDDSC.psd1 b/ADF/0-archive/WVDDSC/WVDDSC.psd1 similarity index 100% rename from ADF/1-prereqs/CustomResources/WVDDSC/WVDDSC.psd1 rename to ADF/0-archive/WVDDSC/WVDDSC.psd1 diff --git a/ADF/1-prereqs/CustomResources/WVDDSC/WVDDSC.psm1 b/ADF/0-archive/WVDDSC/WVDDSC.psm1 similarity index 100% rename from ADF/1-prereqs/CustomResources/WVDDSC/WVDDSC.psm1 rename to ADF/0-archive/WVDDSC/WVDDSC.psm1 diff --git a/ADF/1-prereqs/CustomResources/AVDDSC.zip b/ADF/1-prereqs/CustomResources/AVDDSC.zip new file mode 100644 index 0000000000000000000000000000000000000000..507147e2dc94f082269abb3322f9848a1eb3448c GIT binary patch literal 4822 zcma)A1yoe+x~5@Zl%b@%ySt_BX$grTqy!0Rq&p;ra0sb` z|NZZ;=iGDdz29E@+p*sDJnLKUv)}L8uZ{*f1~JOd@m!cNmgl*L9e@2Fmd^-vK@m$XM7Lcm@DeN4LNj(! z7LlG3Qeow*4?NZ8s<<(G^31*Ml`?{nm<@YaW&Eso9`?h&BdsUM%W2N*FACC!yk-@Q zZIwSVoC-_by}(5jj+K->(Qh2CPOG(FgtyL%C)#)b{Cm~qDCxJhu^gWX%hNC%#blAA zodVoHrUL^D*2h)&^y*UQ5C)a00euqFq@E_0mcc@XSV2VBmX*|8fnUI9?)Nj>KIhE9 zr@Wt1%(W7pec*7P0YA0lQ(GZA8uD#%FiWu1vxl$f+qVwY#hP|~=)Z;u%fP$VaLNZ) z5eQhrna|5L*-etqt2i#PB4<)uyHeaM?W0@4?cSKOBj4pAMk4}oSBk6>h8ln|4{3#I zWeGgI(v5}&gxOOFeUGr_h_H9gIDFJDkm`KI7utg<=%c9(q;SUOaIm8?l1T4b>!!Xu2s%& z$#%m0p6LndV!|REkP#%175j$wH>W{nKB7X-K3;nk{FpImu3OBS5{Uep*`S`7L6MPf z6VhfT#uYy*y=@*%wpn-Q4s3z25j!q1StFv&z1BZ_ToNh9OVNOOD&^guYAk-$o7T-up2ZZ0X~}YAuVz{-vUyQVV8{ z$mYCL5XGJP{+)^-{Bb7!&@n#H?Gkyr0v0)H{eE_pu8M(_&XL<9$~Q4T52y?2>jpI~ zJ#8sOON&}g-E-EL521VhFI&F4hXQSf#n|#w9~ud19=h=qTB_?XB+9%XCU;9#rK=(4 zSA~03S);#I{CbLX*_y5*xSFSXk4qUvSk%uB@+%8=nbs+Mf!^ZA{wP*5Q&Z&wxQVCI zx+Q2I>q5lfh6%iT8V^`QJt-ZwI8rDhwvE;i$f6-W9zCR3Lb|Z;ET4b?6`W1Bp|9$N zxcOG(Q-uH_t7glqtmU6S6%H!*>o!aVV!KR04>-BWt*IIl!f^1NFvjg<9NKjqpcS1b zA@0q=HdHobX;!phK-%7}Tkx=D5VsN`<5_Qy?u;?@?IWTA8gQvv( zefz-e>&ILDP&6M*8#$-$sel4i9RP(>Bbv?>UD4>W#V$L`)l99I|ISp?#lT^9V4X!O zp1#2bHpz~n`HLlp_b8U=ou|O7UGc%@cv>fzRD7e)_SN7$Wnly zP2&wG^tcWbpgL(P48?37-$`EO9Z-p_X`gpe@MqeA80+8$Ox2kBo z4#lH=ege+VE*;t{4(3gAdJdxNmeWmdrHHdqcT;ZDn#LvhP_QIp^4*q2EX&hep8yq= zy+#eN^r$dOXiem#U}>3^f+Ym`e2^d2HZ3J8?rMVJLanmFlhwOmVk~w5lU-02vm%3? zrv5(I8HP(dvJ%$p$yr?)0nRd&tq-j6t)*}7!(PVw{J z^z?Ipl)hE1UXv;VFluOKU&e_bA6YmfGg9x zx40XvcVpS%EdHj-6y-xB6Je7z4Mf-wQYjtD zc@9hil=*pAFygx$dnQx{|R7p|Kg*?TWC3 z`2f@8*H{}Z;VG(dYz8)-d}=^bp#RDFE7!`A6i5CYGwS(|}|bo_f~TC4X;eVD^^Xkw*23(m68ogSjM67%m4-_A{4=igGRhX~y6v zee3@BsSJqlrSaD`EAVC6Bd>-~cet-W*C(ZYa>j|$4uDj z+mJps(k5F>qbQMXyPguu$<3;y#%oM%)rlbO@W2Y)m(_r>*(G1;3{Sm$fpnpV zGO|9=P}GbFXGNYE~Nem~bCEj~#k9y(a+6mk?)>9ldH^{oL#`ujlI{_x! zVG#c8$8yw=3FfPgP++)#ifvqBlyPU5@KNgWZ5`RrXo#goWg7k_8t(Ii@BxtovdlW_ zwD}>^ZvIGiJ`t6mRU2tzZAt_|Dv&*g1q^-78It80Dd9y6o7tZz47#!5(=W#39wWcX zqO#>3mK3j+Ah9FB-sP(Q+;0LZ628D@h_10_G%p@Nj%RB%zd%~^?^%koxW)yvSN9lB zLh06cJLl^*WCgv_#kG3Nlxl-!_{w}*sa8HpC@FNW&$p_lC7XMeN{5LN?|vdSehG7m zSLBEeMuSg<+d&Y&q^prG9UWzaO$ z6qK~}m7eHnBxLe|+8B>$mBl@o>mX+{nM7G}h8>^8GB;H_FdDn#tDWj@+rY=$M#*o! zw6^udda%%s#|dcn=u%beT?9__Eq1{+0ak6T0pUdg)#F{fkSyUmn8BKwB&-5;Ki9ZG zqP5GdJkA(`wopPmb3dbvCV>O>S)K^uxlutEs{diV$ShS}1Xj~3j(K2$*s#})UIK25 zc}e#BxJm+$*_SXU^DBYbX0*XGPu&6`quiF|7Co}`- z0FL88{TL3K4Y{1}nr_rN#>=ZXwydxuQ)e{RxoT>UJKfGp4NhL2xv+LoQD=zm5XTJY{%37$)JFX-NU0|_>tOCtU zyT>_{-n2HhKafzxg(W7qc@qui4@K36S$oBYO(e`!Y`CffjW#`IboYjw=yMVucsyL< z3wBnh<$B&j@HHQmfazm&s(+`ID4~dy20KeCaed+wW%TJimt=wz);CF6tz*8?jKJBh zw#AwqTd{t>`ut4Hb<=6mw!Ixh>EV%I_YS!xp{_ic_YJSh*zQB+Ql3Wk`}C zkBgn!A~(djsZO-W$%}gPi8MeSmRJ}NUCEB$t(V7Z@C`?BouFuoK@kx)wylm6L9whh zP8Y8nc+8!j#Dz0GVB-5qB;ZoMF#lD7U`zPhjQ6OCOHqof6{i4Kl#cyiEQuF>ewFSuS($q;Rbwd!MD=$DzSZCgS(eb{4@rBLU572f~=Iz*?1H!@TuS=MHMLpYsup6&7{!tEB7)u1b$337M4C{LwhyqS3 zM(5tQFvVY~yoQb}(pj!pIP(i&;1B4n#u7%Y_qT8wBukuHxG06|yhtTyfAwh^-jU`j z2Fa0mxgvuwTWDMi_w;kyF*{JYjPtx`2;B7IT{tT$+S;)LmHx`nRu@hhn)HEQq_ z)6D+Rrk!lGfA;|ZDjnE7$SLfbfT@krWoT|^yLmron3b$duI(&bJ4@2l44G^&Kkfgz zQxq69<=g|fAc>IQr=@XqLW{D27glVL&_+?1DaNY;>hOf$ zGEUhmBo$wyo%j>8c$R-j`j-A$NIi>j9;TQ~{`Ntj_VNCEThV?&jJc~txp9kKp2VQo zQhp;R63E8D1?Vg6NDS{xk3WY-@^E^KS$KoJoz~5GLrB!tSG0u@E6_v<@qD5Q)tQWh z0bGS~YytI1L#a~SusLZqQLs@1m{iFl?HkSlJ~Sr9pE&Y5m-M~Tk~dj6uk?t`2U{F( z0l}7-Ac*l)w%zpxLq`J@O%~-pwKtgV zs?E>wv+DdDe>FRBQRx1BL`5M(@e*WsOIbXN`sa(^reDDSt|ju{z&~-A@6Nw~e`}EZ z&cS;|8rGcjzjOT0ALidVT<-Szf8~Ji|IYFMUB^E=hu=AR0e^G+JN5>bPIMGNq5Uf)Ibb@Xft(?Icdi zOIC8<=giD`{Jegy@4Byh;g=|W4;XtIYOYi>Em>vCHPUBYYRoF!JSr~H>l3dGg%8MYp( zqjPondOE?<42zkX>~C`Iax&Ce@R`GK&dUDZ;pc_E^3rUr!M5FPYgX=@_j4?yyqvP0 zVl_i8?^?SF|5er{pLNDnrkggad<$evcs{OSXiTqBA_2z&6xM$U4zI2EC&n?+auice z$Luac9K2JH)G8JjUh4hIezCJ_j0zs>NNv7Bi1d*fI?$n7tQvZ%C-`qOJ6?SaPJGr5 z>sB~zF(2<>+JTKR#~i&)?J9@$SG(|gKXN!lAJ<2YqW0nZI0-&uzBtok{OfbXcQbO! z8E=&%+Al%-(Q5U(*w<~n!Dfb?Iu8F2`wZ0X4i_kG?ylEZ-S+MLNl+!Sx;ul+VAEn9 z{zCmnXupSt1MkGR>U>C%YTewJ!b3Rn$AF#VZ#ioy=Wc#hj82Z zc7-B|-iDc9^oXl_g4J_8FTzdpwR-LtX_);9r#kEPeJwIPyrrL&S^hVS+#Q^;|H8%* Qs$=wOWxGUJjn@zT0vkUX!vFvP literal 0 HcmV?d00001 diff --git a/ADF/1-prereqs/CustomResources/AVDDSC/AVDDSC.psm1 b/ADF/1-prereqs/CustomResources/AVDDSC/AVDDSC.psm1 new file mode 100644 index 00000000..20ee856e --- /dev/null +++ b/ADF/1-prereqs/CustomResources/AVDDSC/AVDDSC.psm1 @@ -0,0 +1,148 @@ +# Defines the values for the resource's Ensure property. +enum Ensure +{ + # The resource must be absent. + Absent + # The resource must be present. + Present +} + +# [DscResource()] indicates the class is a DSC resource. +[DscResource()] +class AVDDSC +{ + + # A DSC resource must define at least one key property. + [DscProperty(Key)] + [string]$PoolNameSuffix + + [DscProperty()] + [string]$ManagedIdentityClientID + + [DscProperty()] + [string]$ResourceGroupName + + [DscProperty()] + [string]$SubscriptionID + + [DscProperty()] + [string]$LogDirectory + + [DscProperty(Key)] + [string]$PackagePath + + # Mandatory indicates the property is required and DSC will guarantee it is set. + [DscProperty()] + [Ensure]$Ensure = [Ensure]::Present + + # Tests if the resource is in the desired state. + [bool] Test() + { + try + { + return (Test-Path 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\RDInfraAgent') + } + catch + { + $ErrMsg = $PSItem | Format-List -Force | Out-String + Write-Log -Err $ErrMsg + throw [System.Exception]::new("Some error occurred in DSC ExecuteRdAgentInstallClient TestScript: $ErrMsg", $PSItem.Exception) + } + } + + # Sets the desired state of the resource. + [void] Set() + { + if (Test-Path -Path $this.PackagePath) + { + $joinKey = $this.GetHostPoolConnectionToken() + $argumentList += " /i $($this.PackagePath) " + $argumentList += " /qb /norestart /l*+ $($this.LogDirectory)\Microsoft.RDInfra.RDAgent.Installer.log" + $argumentList += " REGISTRATIONTOKEN=$joinKey" + + $retryTimeToSleepInSec = 30 + $retryCount = 0 + $sts = $null + do + { + if ($retryCount -gt 0) + { + Start-Sleep -Seconds $retryTimeToSleepInSec + } + + $processResult = Start-Process -FilePath 'msiexec.exe' -ArgumentList $argumentList -Wait -PassThru + $sts = $processResult.ExitCode + + $retryCount++ + } + while ($sts -eq 1618 -and $retryCount -lt 20) # Error code 1618 is ERROR_INSTALL_ALREADY_RUNNING see https://docs.microsoft.com/en-us/windows/win32/msi/-msiexecute-mutex . + } + else + { + throw "Package not found at $($this.PackagePath)" + } + } + + # Gets the resource's current state. + [AVDDSC] Get() + { + # Return this instance or construct a new instance. + return $this + } + + <# + Helper method to Get the ResourceID + #> + + [string] GetHostPoolConnectionToken() + { + #region Retrieve the token via the ManagedIdentity + $WebRequest = @{ + UseBasicParsing = $true + Uri = "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&client_id=$($this.ManagedIdentityClientID)&resource=https://management.azure.com/" + Method = 'GET' + Headers = @{Metadata = 'true' } + ErrorAction = 'Stop' + ContentType = 'application/json' + } + $response = Invoke-WebRequest @WebRequest + $ArmToken = $response.Content | ConvertFrom-Json | ForEach-Object access_token + #endregion retrieve token + + #region only check the metadata service if details not passed in. + if (-not $this.SubscriptionID -or -not $this.ResourceGroupName) + { + $URI = 'http://169.254.169.254/metadata/instance?api-version=2019-02-01' + $VMMeta = Invoke-RestMethod -Headers @{'Metadata' = 'true' } -Uri $URI -Method GET + $Compute = $VMMeta.compute + + if (-not $this.SubscriptionID) + { + $this.SubscriptionID = $Compute.subscriptionId + } + + if (-not $this.ResourceGroupName) + { + $this.ResourceGroupName = $Compute.resourceGroupName + } + } + #endregion retrieve optional information. + + $Deployment = $this.ResourceGroupName -replace '-RG','' + $PoolName = "{0}-avdhp{1}" -f $Deployment,$this.PoolNameSuffix + $WebRequest['Headers'] = @{ Authorization = "Bearer $ArmToken" } + $WebRequest['Uri'] = "https://management.azure.com/subscriptions/$($this.SubscriptionId)/resourceGroups/$($this.ResourceGroupName)/providers/Microsoft.DesktopVirtualization/hostPools/$($PoolName)?api-version=2019-12-10-preview" + + + $Pool = (Invoke-WebRequest @WebRequest).content | ConvertFrom-Json + $HostPoolConnectionToken = $Pool | ForEach-Object properties | ForEach-Object RegistrationInfo | ForEach-Object token + if ($HostPoolConnectionToken) + { + return $HostPoolConnectionToken + } + else + { + throw "Registration token must be generated first, cannot continue" + } + } +} \ No newline at end of file diff --git a/ADF/1-prereqs/CustomResources/AVDDSC/LICENSE b/ADF/1-prereqs/CustomResources/AVDDSC/LICENSE new file mode 100644 index 00000000..d0011860 --- /dev/null +++ b/ADF/1-prereqs/CustomResources/AVDDSC/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Ben Wilkinson + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/ADF/1-prereqs/CustomResources/AVDDSC/README.md b/ADF/1-prereqs/CustomResources/AVDDSC/README.md new file mode 100644 index 00000000..530c0d6f --- /dev/null +++ b/ADF/1-prereqs/CustomResources/AVDDSC/README.md @@ -0,0 +1,95 @@ +# WVDDSC + +PowerShell Web Access DSC __Class based Resource__ + +This is a DSC Resource for configuring Windows Virtual Destkop Host Pool (WVD) + +__Requirements__ +* PowerShell Version 5.0 + +* Server 2012 + + +```powershell + # sample configuation data + + DirectoryPresentSource = @( + @{ + filesSourcePath = '\\{0}.file.core.windows.net\source\WVD\' + filesDestinationPath = 'F:\Source\WVD\' + MatchSource = $true + } + ) + + SoftwarePackagePresent = @( + @{ + Name = 'Remote Desktop Agent Boot Loader' + Path = 'F:\Source\WVD\Microsoft.RDInfra.RDAgentBootLoader.Installer-x64.msi' + ProductId = '{41439A3F-FED7-478A-A71B-8E15AF8A6607}' + Arguments = '/log "F:\Source\WVD\AgentBootLoaderInstall.txt"' + } + + WVDInstall = @( + @{ + PoolNameSuffix = 'hp01' + PackagePath = 'F:\Source\WVD\Microsoft.RDInfra.RDAgent.Installer-x64-1.0.2548.6500.msi' + } + ) +``` + + +```powershell + + $StringFilter = '\W', '' + #------------------------------------------------------------------- + foreach ($File in $Node.DirectoryPresentSource) + { + $Name = ($File.filesSourcePath -f $StorageAccountName + $File.filesDestinationPath) -replace $StringFilter + File $Name + { + SourcePath = ($File.filesSourcePath -f $StorageAccountName) + DestinationPath = $File.filesDestinationPath + Ensure = 'Present' + Recurse = $true + Credential = $StorageCred + MatchSource = IIF $File.MatchSource $File.MatchSource $False + } + $dependsonDirectory += @("[File]$Name") + } + + #------------------------------------------------------------------- + # install any packages without dependencies + foreach ($Package in $Node.SoftwarePackagePresent) + { + $Name = $Package.Name -replace $StringFilter + xPackage $Name + { + Name = $Package.Name + Path = $Package.Path + Ensure = 'Present' + ProductId = $Package.ProductId + PsDscRunAsCredential = $credlookup['DomainCreds'] + DependsOn = $dependsonDirectory + Arguments = $Package.Arguments + } + + $dependsonPackage += @("[xPackage]$($Name)") + } + + #------------------------------------------------------------------- + # install WVD package + if ($Node.WVDInstall) + { + WVDDSC RDInfraAgent + { + PoolNameSuffix = $Node.WVDInstall.PoolNameSuffix + PackagePath = $Node.WVDInstall.PackagePath + ManagedIdentityClientID = $AppInfo.ClientID + } + } +``` + +Full sample available here + +- DSC Configuration + - [ADF/ext-DSC/DSC-AppServers.ps1](https://github.com/brwilkinson/AzureDeploymentFramework/blob/main/ADF/ext-DSC/DSC-AppServers.ps1#L7121) +- DSC ConfigurationData + - [ADF/ext-CD/WVD-ConfigurationData.psd1](https://github.com/brwilkinson/AzureDeploymentFramework/blob/main/ADF/ext-CD/WVD-ConfigurationData.psd1#L38) \ No newline at end of file diff --git a/ADF/bicep/00-ALL-MG.bicep b/ADF/bicep/00-ALL-MG.bicep index 3ccf6a74..561d8c5c 100644 --- a/ADF/bicep/00-ALL-MG.bicep +++ b/ADF/bicep/00-ALL-MG.bicep @@ -6,6 +6,9 @@ 'AWU2' 'AWU3' 'AWCU' + 'UGAZ' + 'UGTX' + 'ASA1' ]) param Prefix string diff --git a/ADF/bicep/00-ALL-SUB.bicep b/ADF/bicep/00-ALL-SUB.bicep index a15f91b1..b3c10092 100644 --- a/ADF/bicep/00-ALL-SUB.bicep +++ b/ADF/bicep/00-ALL-SUB.bicep @@ -6,6 +6,9 @@ 'AWU2' 'AWU3' 'AWCU' + 'UGAZ' + 'UGTX' + 'ASA1' ]) param Prefix string diff --git a/ADF/bicep/01-ALL-RG.bicep b/ADF/bicep/01-ALL-RG.bicep index c9a32bba..9b8804e9 100644 --- a/ADF/bicep/01-ALL-RG.bicep +++ b/ADF/bicep/01-ALL-RG.bicep @@ -6,6 +6,9 @@ 'AWU2' 'AWU3' 'AWCU' + 'UGAZ' + 'UGTX' + 'ASA1' ]) param Prefix string @@ -285,23 +288,23 @@ module dp_Deployment_DNSResolver 'DNSResolver.bicep' = if (bool(Stage.?DNSResolv ] } -// /* -// module dp_Deployment_CloudTestAccount 'CloudTestAccount.bicep' = if (bool(Stage.?CloudTestAccount ?? 0)) { -// name: 'dp${Deployment}-CloudTestAccount' -// params: { -// // move these to Splatting later -// DeploymentID: DeploymentID -// DeploymentInfo: DeploymentInfo -// Environment: Environment -// Extensions: Extensions -// Global: Global -// Prefix: Prefix -// Stage: Stage -// } -// dependsOn: [ -// dp_Deployment_VNET -// ] -// } +/* +module dp_Deployment_CloudTestAccount 'CloudTestAccount.bicep' = if (bool(Stage.?CloudTestAccount ?? 0)) { + name: 'dp${Deployment}-CloudTestAccount' + params: { + // move these to Splatting later + DeploymentID: DeploymentID + DeploymentInfo: DeploymentInfo + Environment: Environment + Extensions: Extensions + Global: Global + Prefix: Prefix + Stage: Stage + } + dependsOn: [ + dp_Deployment_VNET + ] +} // module dp_Deployment_CloudTestImages 'CloudTestImage.bicep' = if (bool(Stage.?CloudTestImages ?? 0)) { // name: 'dp${Deployment}-CloudTestImages' @@ -648,6 +651,59 @@ module dp_Deployment_TM 'TM.bicep' = if (bool(Stage.?TM ?? 0)) { ] } +module dp_Deployment_AVDHostPool 'AVDHostPool.bicep' = if (bool(Stage.?AVDHostPool ?? 0)) { + name: 'dp${Deployment}-AVDHostPool' + params: { + // move these to Splatting later + DeploymentID: DeploymentID + DeploymentInfo: DeploymentInfo + Environment: Environment + Extensions: Extensions + Global: Global + Prefix: Prefix + Stage: Stage + } + dependsOn: [ + dp_Deployment_OMS + ] +} + +module dp_Deployment_ADVAppGroup 'AVDAppGroup.bicep' = if (bool(Stage.?AVDHostAppGroup ?? 0)) { + name: 'dp${Deployment}-AVDHostAppGroup' + params: { + // move these to Splatting later + DeploymentID: DeploymentID + DeploymentInfo: DeploymentInfo + Environment: Environment + Extensions: Extensions + Global: Global + Prefix: Prefix + Stage: Stage + } + dependsOn: [ + dp_Deployment_OMS + dp_Deployment_AVDHostPool + ] +} + +module dp_Deployment_WorkSpace 'AVDWorkspace.bicep' = if (bool(Stage.?AVDHostWorkSpace ?? 0)) { + name: 'dp${Deployment}-AVDHostWorkSpace' + params: { + // move these to Splatting later + DeploymentID: DeploymentID + DeploymentInfo: DeploymentInfo + Environment: Environment + Extensions: Extensions + Global: Global + Prefix: Prefix + Stage: Stage + } + dependsOn: [ + dp_Deployment_OMS + dp_Deployment_ADVAppGroup + ] +} + // This is used for Promotion of Domain Controllers module dp_Deployment_VNETDNSPublic 'x.setVNET.bicep' = if (bool(Stage.?ADPrimary ?? 0) || bool(Stage.?CreateADPDC ?? 0)) { name: 'dp${Deployment}-VNETDNSPublic' @@ -765,6 +821,7 @@ module AppServers 'VM.bicep' = if (bool(Stage.?VMApp ?? 0)) { dp_Deployment_LB // DNSLookup dp_Deployment_SA + dp_Deployment_AVDHostPool ] } diff --git a/ADF/bicep/AVDAppGroup-AG-App.bicep b/ADF/bicep/AVDAppGroup-AG-App.bicep new file mode 100644 index 00000000..e3dcf042 --- /dev/null +++ b/ADF/bicep/AVDAppGroup-AG-App.bicep @@ -0,0 +1,51 @@ +param Deployment string +param DeploymentURI string +param AVDAppGroupName string +param AVDAppGroupAppInfo object +param Global object +param Prefix string +param Environment string +param DeploymentID string +param Stage object +param OMSID string + +resource AppGroup 'Microsoft.DesktopVirtualization/applicationGroups@2024-01-16-preview' existing = { + name: AVDAppGroupName + + resource application 'applications@2024-01-16-preview' = { + name: AVDAppGroupAppInfo.description + properties: { + description: AVDAppGroupAppInfo.description + friendlyName: AVDAppGroupAppInfo.friendlyName + filePath: AVDAppGroupAppInfo.filePath + commandLineSetting: AVDAppGroupAppInfo.commandLineSetting + commandLineArguments: AVDAppGroupAppInfo.commandLineArguments + showInPortal: AVDAppGroupAppInfo.showInPortal + iconPath: AVDAppGroupAppInfo.iconPath + iconIndex: AVDAppGroupAppInfo.iconIndex + applicationType: AVDAppGroupAppInfo.applicationType + } + } +} + +// resource AppGroupDiags 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = { +// name: 'service' +// scope: AppGroup::application +// properties: { +// workspaceId: OMSID +// logs: [ +// { +// enabled: true +// category: 'Checkpoint' +// } +// { +// enabled: true +// category: 'Error' +// } +// { +// enabled: true +// category: 'Management' +// } +// ] +// } +// } diff --git a/ADF/bicep/AVDAppGroup-AG.bicep b/ADF/bicep/AVDAppGroup-AG.bicep new file mode 100644 index 00000000..ecb2af25 --- /dev/null +++ b/ADF/bicep/AVDAppGroup-AG.bicep @@ -0,0 +1,88 @@ +param Deployment string +param DeploymentURI string +param AVDAppGroupInfo object +param Global object +param Prefix string +param Environment string +param DeploymentID string +param Stage object +param OMSID string + + +resource hostPool 'Microsoft.DesktopVirtualization/hostPools@2024-01-16-preview' existing = { + name: '${Deployment}-avdhp${AVDAppGroupInfo.HostPoolName}' +} + +resource AppGroup 'Microsoft.DesktopVirtualization/applicationGroups@2024-01-16-preview' = { + name: '${Deployment}-avdag${AVDAppGroupInfo.Name}' + location: AVDAppGroupInfo.?location ?? resourceGroup().location + kind: AVDAppGroupInfo.Kind + properties: { + hostPoolArmPath: hostPool.id + description: AVDAppGroupInfo.description + friendlyName: AVDAppGroupInfo.friendlyName + applicationGroupType: AVDAppGroupInfo.Kind + } +} + +var users = AVDAppGroupInfo.?DesktopVirtualizationUser ?? [] + +module RBAC 'x.RBAC-ALL.bicep' = [for (user, index) in users: { + name: take(replace('dp-rbac-role-${AVDAppGroupInfo.name}-VDI-User-${user}', '@', '_'), 64) + params: { + resourceId: AppGroup.id + Global: Global + roleInfo: { + Name: user + RBAC: [ + { + Name: 'Desktop Virtualization User' + } + ] + } + Type: 'lookup' + deployment: Deployment + } +}] + +resource AppGroupDiags 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = { + name: 'service' + scope: AppGroup + properties: { + workspaceId: OMSID + logs: [ + { + enabled: true + category: 'Checkpoint' + } + { + enabled: true + category: 'Error' + } + { + enabled: true + category: 'Management' + } + ] + } +} + +var AVDAppGroupApplications = AVDAppGroupInfo.?Applications ?? [] + +module AppGroupApplications 'AVDAppGroup-AG-App.bicep' = [for (aga,index) in AVDAppGroupApplications : { + name: '${Deployment}-avdag${AVDAppGroupInfo.Name}-${aga.description}' + params: { + Deployment: Deployment + DeploymentURI: DeploymentURI + AVDAppGroupAppInfo: aga + AVDAppGroupName: '${Deployment}-avdag${AVDAppGroupInfo.Name}' + Global: Global + DeploymentID: DeploymentID + Environment: Environment + Prefix: Prefix + Stage: Stage + OMSID: OMSID + } +}] + + diff --git a/ADF/bicep/AVDAppGroup.bicep b/ADF/bicep/AVDAppGroup.bicep new file mode 100644 index 00000000..aca9745e --- /dev/null +++ b/ADF/bicep/AVDAppGroup.bicep @@ -0,0 +1,95 @@ +param Prefix string + +@allowed([ + 'I' + 'D' + 'T' + 'U' + 'P' + 'S' + 'G' + 'A' +]) +param Environment string = 'D' + +@allowed([ + '0' + '1' + '2' + '3' + '4' + '5' + '6' + '7' + '8' + '9' + '10' + '11' + '12' + '13' + '14' + '15' + '16' +]) +param DeploymentID string +#disable-next-line no-unused-params +param Stage object +#disable-next-line no-unused-params +param Extensions object +param Global object = { + n: '1' +} +param DeploymentInfo object + + +var Deployment = '${Prefix}-${Global.OrgName}-${Global.Appname}-${Environment}${DeploymentID}' +var DeploymentURI = toLower('${Prefix}${Global.OrgName}${Global.Appname}${Environment}${DeploymentID}') + +var HubRGJ = json(Global.hubRG) +var HubKVJ = json(Global.hubKV) + +var gh = { + hubRGPrefix: HubRGJ.?Prefix ?? Prefix + hubRGOrgName: HubRGJ.?OrgName ?? Global.OrgName + hubRGAppName: HubRGJ.?AppName ?? Global.AppName + hubRGRGName: HubRGJ.?name ?? HubRGJ.?name ?? '${Environment}${DeploymentID}' + + hubKVPrefix: contains(HubKVJ, 'Prefix') ? HubKVJ.Prefix : Prefix + hubKVOrgName: contains(HubKVJ, 'OrgName') ? HubKVJ.OrgName : Global.OrgName + hubKVAppName: contains(HubKVJ, 'AppName') ? HubKVJ.AppName : Global.AppName + hubKVRGName: contains(HubKVJ, 'RG') ? HubKVJ.RG : HubRGJ.name +} + +var HubRGName = '${gh.hubRGPrefix}-${gh.hubRGOrgName}-${gh.hubRGAppName}-RG-${gh.hubRGRGName}' +var HubKVName = toLower('${gh.hubKVPrefix}-${gh.hubKVOrgName}-${gh.hubKVAppName}-${gh.hubKVRGName}-kv${HubKVJ.name}') + +resource KV 'Microsoft.KeyVault/vaults@2021-06-01-preview' existing = { + name: HubKVName + scope: resourceGroup(HubRGName) +} + +resource OMS 'Microsoft.OperationalInsights/workspaces@2022-10-01' existing = { + name: '${DeploymentURI}LogAnalytics' +} + +var AVDAppGroupInfo = DeploymentInfo.?AVDAppGroupInfo ?? [] + +var AppGroup = [for (ag,index) in AVDAppGroupInfo : { + match: ((Global.CN == '.') || contains(array(Global.CN), ag.Name)) +}] + +module AVDWorkspace 'AVDAppGroup-AG.bicep' = [for (ag,index) in AVDAppGroupInfo : if(AppGroup[index].match) { + name: 'dp${Deployment}-AVDHostPoolDeploy${ag.name}' + params: { + Deployment: Deployment + DeploymentURI: DeploymentURI + AVDAppGroupInfo: ag + Global: Global + DeploymentID: DeploymentID + Environment: Environment + Prefix: Prefix + Stage: Stage + OMSID: OMS.id + } +}] + diff --git a/ADF/bicep/AVDHostPool-HP.bicep b/ADF/bicep/AVDHostPool-HP.bicep new file mode 100644 index 00000000..560a7c78 --- /dev/null +++ b/ADF/bicep/AVDHostPool-HP.bicep @@ -0,0 +1,92 @@ +param Deployment string +param DeploymentURI string +param AVDHostPoolInfo object +param Global object +param Prefix string +param Environment string +param DeploymentID string +param Stage object +param OMSID string + + +resource hostPool 'Microsoft.DesktopVirtualization/hostPools@2024-01-16-preview' = { + name: '${Deployment}-avdhp${AVDHostPoolInfo.Name}' + location: AVDHostPoolInfo.?location ?? resourceGroup().location + properties: { + description: AVDHostPoolInfo.Description + managedPrivateUDP: 'Default' + directUDP: 'Default' + publicUDP: 'Default' + relayUDP: 'Default' + managementType: 'Standard' + hostPoolType: 'Pooled' + customRdpProperty: 'targetisaadjoined:i:1;drivestoredirect:s:;audiomode:i:0;videoplaybackmode:i:1;redirectclipboard:i:0;redirectprinters:i:0;devicestoredirect:s:;redirectcomports:i:0;redirectsmartcards:i:1;usbdevicestoredirect:s:;enablecredsspsupport:i:0;redirectwebauthn:i:1;use multimon:i:1;enablerdsaadauth:i:1;camerastoredirect:s:;redirectlocation:i:1;keyboardhook:i:1;' + maxSessionLimit: 10 + loadBalancerType: 'BreadthFirst' + validationEnvironment: false + // vmTemplate: '{"domain":"","galleryImageOffer":"windows-11","galleryImagePublisher":"microsoftwindowsdesktop","galleryImageSKU":"win11-23h2-avd","imageType":"Gallery","customImageId":null,"namePrefix":"VDTEast2","osDiskType":"StandardSSD_LRS","vmSize":{"id":"Standard_B2s","cores":2,"ram":4,"rdmaEnabled":false,"supportsMemoryPreservingMaintenance":true},"galleryItemId":"microsoftwindowsdesktop.windows-11win11-23h2-avd","hibernate":false,"diskSizeGB":128,"securityType":"TrustedLaunch","secureBoot":true,"vTPM":true,"vmInfrastructureType":"Cloud","virtualProcessorCount":null,"memoryGB":null,"maximumMemoryGB":null,"minimumMemoryGB":null,"dynamicMemoryConfig":false}' + preferredAppGroupType: 'Desktop' + startVMOnConnect: false + agentUpdate: { + maintenanceWindowTimeZone: Global.schedulerTimeZone + type: 'Scheduled' + maintenanceWindows: [ + { + dayOfWeek: 'Sunday' + hour: 3 + } + ] + } + } +} + +resource HostPoolDiags 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = { + name: 'service' + scope: hostPool + properties: { + workspaceId: OMSID + logs: [ + { + enabled: true + category: 'Checkpoint' + } + { + enabled: true + category: 'Error' + } + { + enabled: true + category: 'Management' + } + { + enabled: true + category: 'Connection' + } + { + enabled: true + category: 'HostRegistration' + } + { + enabled: true + category: 'AgentHealthStatus' + } + { + enabled: true + category: 'NetworkData' + } + { + enabled: true + category: 'ConnectionGraphicsData' + } + { + enabled: true + category: 'SessionHostManagement' + } + { + enabled: true + category: 'AutoscaleEvaluationPooled' + } + ] + } +} + diff --git a/ADF/bicep/AVDHostPool.bicep b/ADF/bicep/AVDHostPool.bicep new file mode 100644 index 00000000..12f9dced --- /dev/null +++ b/ADF/bicep/AVDHostPool.bicep @@ -0,0 +1,95 @@ +param Prefix string + +@allowed([ + 'I' + 'D' + 'T' + 'U' + 'P' + 'S' + 'G' + 'A' +]) +param Environment string = 'D' + +@allowed([ + '0' + '1' + '2' + '3' + '4' + '5' + '6' + '7' + '8' + '9' + '10' + '11' + '12' + '13' + '14' + '15' + '16' +]) +param DeploymentID string +#disable-next-line no-unused-params +param Stage object +#disable-next-line no-unused-params +param Extensions object +param Global object = { + n: '1' +} +param DeploymentInfo object + + +var Deployment = '${Prefix}-${Global.OrgName}-${Global.Appname}-${Environment}${DeploymentID}' +var DeploymentURI = toLower('${Prefix}${Global.OrgName}${Global.Appname}${Environment}${DeploymentID}') + +var HubRGJ = json(Global.hubRG) +var HubKVJ = json(Global.hubKV) + +var gh = { + hubRGPrefix: HubRGJ.?Prefix ?? Prefix + hubRGOrgName: HubRGJ.?OrgName ?? Global.OrgName + hubRGAppName: HubRGJ.?AppName ?? Global.AppName + hubRGRGName: HubRGJ.?name ?? HubRGJ.?name ?? '${Environment}${DeploymentID}' + + hubKVPrefix: contains(HubKVJ, 'Prefix') ? HubKVJ.Prefix : Prefix + hubKVOrgName: contains(HubKVJ, 'OrgName') ? HubKVJ.OrgName : Global.OrgName + hubKVAppName: contains(HubKVJ, 'AppName') ? HubKVJ.AppName : Global.AppName + hubKVRGName: contains(HubKVJ, 'RG') ? HubKVJ.RG : HubRGJ.name +} + +var HubRGName = '${gh.hubRGPrefix}-${gh.hubRGOrgName}-${gh.hubRGAppName}-RG-${gh.hubRGRGName}' +var HubKVName = toLower('${gh.hubKVPrefix}-${gh.hubKVOrgName}-${gh.hubKVAppName}-${gh.hubKVRGName}-kv${HubKVJ.name}') + +resource KV 'Microsoft.KeyVault/vaults@2021-06-01-preview' existing = { + name: HubKVName + scope: resourceGroup(HubRGName) +} + +resource OMS 'Microsoft.OperationalInsights/workspaces@2022-10-01' existing = { + name: '${DeploymentURI}LogAnalytics' +} + +var AVDHostPoolInfo = DeploymentInfo.?AVDHostPoolInfo ?? [] + +var HostPool = [for (hp,index) in AVDHostPoolInfo : { + match: ((Global.CN == '.') || contains(array(Global.CN), hp.Name)) +}] + +module AVDHostPool 'AVDHostPool-HP.bicep' = [for (hp,index) in AVDHostPoolInfo : if(HostPool[index].match) { + name: 'dp${Deployment}-AVDHostPoolDeploy${hp.name}' + params: { + Deployment: Deployment + DeploymentURI: DeploymentURI + AVDHostPoolInfo: hp + Global: Global + DeploymentID: DeploymentID + Environment: Environment + Prefix: Prefix + Stage: Stage + OMSID: OMS.id + } +}] + diff --git a/ADF/bicep/AVDWorkspace-WS.bicep b/ADF/bicep/AVDWorkspace-WS.bicep new file mode 100644 index 00000000..1da405d3 --- /dev/null +++ b/ADF/bicep/AVDWorkspace-WS.bicep @@ -0,0 +1,54 @@ +param Deployment string +param DeploymentURI string +param AVDWorkspaceInfo object +param Global object +param Prefix string +param Environment string +param DeploymentID string +param Stage object +param OMSID string + +var AVDAppGroupName = AVDWorkspaceInfo.?AppGroupName ?? [] + +resource AppGroup 'Microsoft.DesktopVirtualization/applicationGroups@2024-01-16-preview' existing = [for (ag, index) in AVDAppGroupName: { + name: '${Deployment}-avdag${ag}' +}] + +resource avdworkspace 'Microsoft.DesktopVirtualization/workspaces@2024-01-16-preview' = { + name: '${Deployment}-avdws${AVDWorkspaceInfo.Name}' + location: AVDWorkspaceInfo.?location ?? resourceGroup().location + properties: { + publicNetworkAccess: AVDWorkspaceInfo.publicNetworkAccess + description: AVDWorkspaceInfo.description + friendlyName: AVDWorkspaceInfo.friendlyName + applicationGroupReferences: [for (ag, index) in AVDAppGroupName: AppGroup[index].id] + } +} + +resource SQLDBDiags 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = { + name: 'service' + scope: avdworkspace + properties: { + workspaceId: OMSID + logs: [ + { + enabled: true + category: 'Checkpoint' + } + { + enabled: true + category: 'Error' + } + { + enabled: true + category: 'Management' + } + { + enabled: true + category: 'Feed' + } + ] + } +} + +output AVDAppGroupName array = AVDAppGroupName diff --git a/ADF/bicep/AVDWorkspace.bicep b/ADF/bicep/AVDWorkspace.bicep new file mode 100644 index 00000000..4542e3bb --- /dev/null +++ b/ADF/bicep/AVDWorkspace.bicep @@ -0,0 +1,95 @@ +param Prefix string + +@allowed([ + 'I' + 'D' + 'T' + 'U' + 'P' + 'S' + 'G' + 'A' +]) +param Environment string = 'D' + +@allowed([ + '0' + '1' + '2' + '3' + '4' + '5' + '6' + '7' + '8' + '9' + '10' + '11' + '12' + '13' + '14' + '15' + '16' +]) +param DeploymentID string +#disable-next-line no-unused-params +param Stage object +#disable-next-line no-unused-params +param Extensions object +param Global object = { + n: '1' +} +param DeploymentInfo object + + +var Deployment = '${Prefix}-${Global.OrgName}-${Global.Appname}-${Environment}${DeploymentID}' +var DeploymentURI = toLower('${Prefix}${Global.OrgName}${Global.Appname}${Environment}${DeploymentID}') + +var HubRGJ = json(Global.hubRG) +var HubKVJ = json(Global.hubKV) + +var gh = { + hubRGPrefix: HubRGJ.?Prefix ?? Prefix + hubRGOrgName: HubRGJ.?OrgName ?? Global.OrgName + hubRGAppName: HubRGJ.?AppName ?? Global.AppName + hubRGRGName: HubRGJ.?name ?? HubRGJ.?name ?? '${Environment}${DeploymentID}' + + hubKVPrefix: contains(HubKVJ, 'Prefix') ? HubKVJ.Prefix : Prefix + hubKVOrgName: contains(HubKVJ, 'OrgName') ? HubKVJ.OrgName : Global.OrgName + hubKVAppName: contains(HubKVJ, 'AppName') ? HubKVJ.AppName : Global.AppName + hubKVRGName: contains(HubKVJ, 'RG') ? HubKVJ.RG : HubRGJ.name +} + +var HubRGName = '${gh.hubRGPrefix}-${gh.hubRGOrgName}-${gh.hubRGAppName}-RG-${gh.hubRGRGName}' +var HubKVName = toLower('${gh.hubKVPrefix}-${gh.hubKVOrgName}-${gh.hubKVAppName}-${gh.hubKVRGName}-kv${HubKVJ.name}') + +resource KV 'Microsoft.KeyVault/vaults@2021-06-01-preview' existing = { + name: HubKVName + scope: resourceGroup(HubRGName) +} + +resource OMS 'Microsoft.OperationalInsights/workspaces@2022-10-01' existing = { + name: '${DeploymentURI}LogAnalytics' +} + +var AVDWorkspaceInfo = DeploymentInfo.?AVDWorkspaceInfo ?? [] + +var Workspace = [for (ws,index) in AVDWorkspaceInfo : { + match: ((Global.CN == '.') || contains(array(Global.CN), ws.Name)) +}] + +module AVDWorkspace 'AVDWorkspace-WS.bicep' = [for (ws,index) in AVDWorkspaceInfo : if(Workspace[index].match) { + name: 'dp${Deployment}-AVDHostPoolDeploy${ws.name}' + params: { + Deployment: Deployment + DeploymentURI: DeploymentURI + AVDWorkspaceInfo: ws + Global: Global + DeploymentID: DeploymentID + Environment: Environment + Prefix: Prefix + Stage: Stage + OMSID: OMS.id + } +}] + diff --git a/ADF/bicep/ImageBuilderAVD.bicep b/ADF/bicep/ImageBuilderAVD.bicep new file mode 100644 index 00000000..67212e5a --- /dev/null +++ b/ADF/bicep/ImageBuilderAVD.bicep @@ -0,0 +1,337 @@ +param Prefix string + +@allowed([ + 'I' + 'D' + 'U' + 'P' + 'S' + 'G' + 'A' +]) +param Environment string = 'D' + +@allowed([ + '0' + '1' + '2' + '3' + '4' + '5' + '6' + '7' + '8' + '9' + '10' + '11' + '12' + '13' + '14' + '15' + '16' +]) +param DeploymentID string +#disable-next-line no-unused-params +param Stage object +#disable-next-line no-unused-params +param Extensions object +param Global object +param DeploymentInfo object +param now string = utcNow('yyyy-MM-dd_hh-mm') + +param month string = utcNow('MM') +param year string = utcNow('yyyy') + +// Use same PAT token for 3 month blocks, min PAT age is 6 months, max is 9 months +var SASEnd = dateTimeAdd('${year}-${padLeft((int(month) - (int(month) - 1) % 3), 2, '0')}-01', 'P9M') + +// Roll the SAS token one per 3 months, min length of 6 months. +var DSCSAS = saaccountidglobalsource.listServiceSAS('2021-09-01', { + canonicalizedResource: '/blob/${saaccountidglobalsource.name}/${last(split(Global._artifactsLocation, '/'))}' + signedResource: 'c' + signedProtocol: 'https' + signedPermission: 'r' + signedServices: 'b' + signedExpiry: SASEnd + keyToSign: 'key1' + }).serviceSasToken + +var GlobalRGJ = json(Global.GlobalRG) +var GlobalSAJ = json(Global.GlobalSA) +var HubRGJ = json(Global.hubRG) + +var regionLookup = json(loadTextContent('./global/region.json')) +var primaryPrefix = regionLookup[Global.PrimaryLocation].prefix + +var gh = { + globalRGPrefix: contains(GlobalRGJ, 'Prefix') ? GlobalRGJ.Prefix : primaryPrefix + globalRGOrgName: contains(GlobalRGJ, 'OrgName') ? GlobalRGJ.OrgName : Global.OrgName + globalRGAppName: contains(GlobalRGJ, 'AppName') ? GlobalRGJ.AppName : Global.AppName + globalRGName: contains(GlobalRGJ, 'name') ? GlobalRGJ.name : '${Environment}${DeploymentID}' + + globalSAPrefix: contains(GlobalSAJ, 'Prefix') ? GlobalSAJ.Prefix : primaryPrefix + globalSAOrgName: contains(GlobalSAJ, 'OrgName') ? GlobalSAJ.OrgName : Global.OrgName + globalSAAppName: contains(GlobalSAJ, 'AppName') ? GlobalSAJ.AppName : Global.AppName + globalSARGName: contains(GlobalSAJ, 'RG') ? GlobalSAJ.RG : contains(GlobalRGJ, 'name') ? GlobalRGJ.name : '${Environment}${DeploymentID}' + + hubRGPrefix: HubRGJ.?Prefix ?? Prefix + hubRGOrgName: HubRGJ.?OrgName ?? Global.OrgName + hubRGAppName: HubRGJ.?AppName ?? Global.AppName + hubRGRGName: HubRGJ.?name ?? HubRGJ.?name ?? '${Environment}${DeploymentID}' +} + +var globalRGName = '${gh.globalRGPrefix}-${gh.globalRGOrgName}-${gh.globalRGAppName}-RG-${gh.globalRGName}' +var HubRGName = '${gh.hubRGPrefix}-${gh.hubRGOrgName}-${gh.hubRGAppName}-RG-${gh.hubRGRGName}' +var globalSAName = toLower('${gh.globalSAPrefix}${gh.globalSAOrgName}${gh.globalSAAppName}${gh.globalSARGName}sa${GlobalSAJ.name}') + +resource saaccountidglobalsource 'Microsoft.Storage/storageAccounts@2021-04-01' existing = { + #disable-next-line BCP334 + name: globalSAName + scope: resourceGroup(globalRGName) +} + +var Deployment = '${Prefix}-${Global.OrgName}-${Global.Appname}-${Environment}${DeploymentID}' +var DeploymentURI = toLower('${Prefix}${Global.OrgName}${Global.Appname}${Environment}${DeploymentID}') +var OMSworkspaceName = replace('${Deployment}LogAnalytics', '-', '') +var OMSworkspaceID = resourceId('Microsoft.OperationalInsights/workspaces/', OMSworkspaceName) + +// os config now shared across subscriptions +var computeGlobal = json(loadTextContent('./global/Global-ConfigVM.json')) +var OSType = computeGlobal.OSType + +var ImageInfo = DeploymentInfo.?ImageInfo ?? [] +var userAssignedIdentities = { + Default: { + '${resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', '${Deployment}-uaiImageBuilder')}': {} + } + None: {} +} +var image = [for (img, index) in ImageInfo: { + match: ((Global.CN == '.') || contains(array(Global.CN), img.imageName)) + imageName: img.imageName + imageBuildLocation: img.replicationRegions[0] + stagingSubnetId: resourceId(img.stagingVNETRG, 'Microsoft.Network/virtualNetworks/subnets', img.stagingVNET, img.stagingSubnet) +}] + +resource Gallery 'Microsoft.Compute/galleries@2021-07-01' existing = [for (img, index) in ImageInfo: { + name: '${DeploymentURI}Gallery${img.GalleryName}' +}] + +resource IMG 'Microsoft.Compute/galleries/images@2021-07-01' = [for (img, index) in ImageInfo: { + #disable-next-line use-stable-resource-identifiers + name: image[index].imageName + parent: Gallery[index] + location: resourceGroup().location + properties: { + description: img.imageName + osType: OSType[img.OSType].OS + osState: 'Generalized' + hyperVGeneration: 'V2' + features: [ + { + name: 'SecurityType' + value: 'TrustedLaunchSupported' + } + { + name: 'IsAcceleratedNetworkSupported' + value: 'True' + } + { + name: 'DiskControllerTypes' + value: 'SCSI, NVMe' + } + { + name: 'IsHibernateSupported' + value: 'True' + } + ] + identifier: { + publisher: '${DeploymentURI}_${image[index].imageName}' + offer: OSType[img.OSType].imagereference.offer + sku: OSType[img.OSType].imagereference.sku + } + } +}] + +resource IMGTemplate 'Microsoft.VirtualMachineImages/imageTemplates@2023-07-01' = [for (img, index) in ImageInfo: if (bool(image[index].match)) { + name: image[index].imageName + location: image[index].imageBuildLocation + identity: { + type: 'UserAssigned' + userAssignedIdentities: userAssignedIdentities.Default + } + tags: { + AVD_IMAGE_TEMPLATE: 'AVD_IMAGE_TEMPLATE' + } + properties: { + buildTimeoutInMinutes: 360 //img.deployTimeoutmin + vmProfile: { + vmSize: img.vmSize + osDiskSizeGB: OSType[img.OSType].OSDiskGB + vnetConfig: { + subnetId: image[index].stagingSubnetId + } + } + errorHandling: { + onCustomizerError: 'abort' + onValidationError: 'abort' + } + stagingResourceGroup: '${subscription().id}/resourceGroups/${img.stagingVNETRG}_${img.imageName}' //image[index].stagingResourceGroupId + source: { + type: 'PlatformImage' + publisher: OSType[img.OSType].imagereference.publisher + offer: OSType[img.OSType].imagereference.offer + sku: OSType[img.OSType].imagereference.sku + version: 'latest' + planInfo: contains(OSType[img.OSType], 'plan') ? OSType[img.OSType].plan : null + } + customize: [ + { + name: 'avdBuiltInScript_timeZoneRedirection' + runAsSystem: true + runElevated: true + scriptUri: 'https://raw.githubusercontent.com/Azure/RDS-Templates/master/CustomImageTemplateScripts/CustomImageTemplateScripts_2023-11-20/TimezoneRedirection.ps1' + type: 'PowerShell' + } + { + name: 'avdBuiltInScript_configureRdpShortpath' + runAsSystem: true + runElevated: true + scriptUri: 'https://raw.githubusercontent.com/Azure/RDS-Templates/master/CustomImageTemplateScripts/CustomImageTemplateScripts_2023-11-20/RDPShortpath.ps1' + type: 'PowerShell' + } + { + destination: 'C:\\AVDImage\\configureSessionTimeouts.ps1' + name: 'avdBuiltInScript_configureSessionTimeouts' + sourceUri: 'https://raw.githubusercontent.com/Azure/RDS-Templates/master/CustomImageTemplateScripts/CustomImageTemplateScripts_2023-11-20/ConfigureSessionTimeoutsV2.ps1' + type: 'File' + } + { + inline: [ + 'C:\\AVDImage\\configureSessionTimeouts.ps1 -MaxDisconnectionTime "120" -MaxIdleTime "180" -MaxConnectionTime "960" -RemoteAppLogoffTimeLimit "960"' + ] + name: 'avdBuiltInScript_configureSessionTimeouts-parameter' + runAsSystem: true + runElevated: true + type: 'PowerShell' + } + { + name: 'avdBuiltInScript_windowsUpdate' + searchCriteria: '' + type: 'WindowsUpdate' + updateLimit: 0 + } + { + name: 'avdBuiltInScript_windowsUpdate-windowsRestart' + restartCheckCommand: '' + restartCommand: '' + restartTimeout: '' + type: 'WindowsRestart' + } + { + destination: 'C:\\AVDImage\\windowsOptimization.ps1' + name: 'avdBuiltInScript_windowsOptimization' + sourceUri: 'https://raw.githubusercontent.com/Azure/RDS-Templates/master/CustomImageTemplateScripts/CustomImageTemplateScripts_2023-11-20/WindowsOptimization.ps1' + type: 'File' + } + { + inline: [ + 'C:\\AVDImage\\windowsOptimization.ps1 -Optimizations "Edge"' + ] + name: 'avdBuiltInScript_windowsOptimization-parameter' + runAsSystem: true + runElevated: true + type: 'PowerShell' + } + { + name: 'avdBuiltInScript_windowsOptimization-windowsUpdate' + searchCriteria: '' + type: 'WindowsUpdate' + updateLimit: 0 + } + { + name: 'avdBuiltInScript_windowsOptimization-windowsRestart' + restartCheckCommand: '' + restartCommand: '' + restartTimeout: '' + type: 'WindowsRestart' + } + { + name: 'avdBuiltInScript_adminSysPrep' + runAsSystem: true + runElevated: true + scriptUri: 'https://raw.githubusercontent.com/Azure/RDS-Templates/master/CustomImageTemplateScripts/CustomImageTemplateScripts_2023-11-20/AdminSysPrep.ps1' + type: 'PowerShell' + } + ] + distribute: [ + { + type: 'SharedImage' + galleryImageId: IMG[index].id + runOutputName: image[index].imageName + artifactTags: { + source: 'azVmImageBuilder' + baseosimg: OSType[img.OSType].imagereference.sku + } + storageAccountType: 'Standard_ZRS' + replicationRegions: img.replicationRegions + } + ] + } +}] + +/* + +resource SetImageBuild 'Microsoft.Resources/deploymentScripts@2020-10-01' = [for (img, index) in ImageInfo: if (bool(image[index].match)) { + name: 'SetImageBuild-${image[index].imageName}-${image[index].imageBuildLocation}' + identity: { + type: 'UserAssigned' + userAssignedIdentities: userAssignedIdentities.Default + } + location: resourceGroup().location + kind: 'AzurePowerShell' + properties: { + azPowerShellVersion: '5.4' + arguments: ' -ResourceGroupName ${resourceGroup().name} -ImageTemplateName ${image[index].imageName}-${image[index].imageBuildLocation}' + scriptContent: loadTextContent('../bicep/loadTextContext/startImageBuildAsync.ps1') + forceUpdateTag: now + cleanupPreference: 'OnExpiration' + retentionInterval: 'P1D' + timeout: 'PT3M' + } + dependsOn: [ + IMGTemplate + ] +}] + +*/ + +// resource IMGVERSION 'Microsoft.Compute/galleries/images/versions@2021-07-01' = [for (img,index) in ImageInfo : if (img.PublishNow == 1) { +// name: '${DeploymentURI}gallery${img.GalleryName}/${image[index].imageName}/0.0.1' +// location: resourceGroup().location +// properties: { +// publishingProfile: { +// replicaCount: 1 +// excludeFromLatest: false +// targetRegions: [ +// { +// name: resourceGroup().location +// regionalReplicaCount: 1 +// storageAccountType: 'Standard_LRS' +// } +// ] +// } +// storageProfile: { +// source: { +// // uri: +// // id: IMG[index].id //resourceId('Microsoft.Compute/galleries/images', '${image[index].imageName}') +// // /subscriptions/{subscriptionguid}/resourceGroups/ACU1-PE-AOA-RG-G1/providers/Microsoft.Compute/galleries/acu1brwaoag1gallery01/images/vmss2019webnetcore01 +// } +// } +// } +// dependsOn: [ +// SetImageBuild +// ] +// }] + +output Identifier array = [for (img, index) in ImageInfo: IMG[index].id]