workload/scripts/Set-SessionHostConfiguration.ps1 (465 lines of code) (raw):
Param(
[parameter(Mandatory = $false)]
[string]
$IdentityDomainName,
[parameter(Mandatory)]
[string]
$AmdVmSize,
[parameter(Mandatory)]
[string]
$IdentityServiceProvider,
[parameter(Mandatory)]
[string]
$FSLogix,
[parameter(Mandatory = $false)]
[string]
$FSLogixStorageAccountKey,
[parameter(Mandatory = $false)]
[string]
$FSLogixFileShare,
[parameter(Mandatory)]
[string]
$HostPoolRegistrationToken,
[parameter(Mandatory)]
[string]
$NvidiaVmSize,
[parameter(Mandatory = $false)]
[string]
$ExtendOsDisk
# [parameter(Mandatory)]
# [string]
# $ScreenCaptureProtection
)
function New-Log {
Param (
[Parameter(Mandatory = $true, Position = 0)]
[string] $Path
)
$date = Get-Date -UFormat "%Y-%m-%d %H-%M-%S"
Set-Variable logFile -Scope Script
$script:logFile = "$Script:Name-$date.log"
if ((Test-Path $path ) -eq $false) {
$null = New-Item -Path $path -ItemType directory
}
$script:Log = Join-Path $path $logfile
Add-Content $script:Log "Date`t`t`tCategory`t`tDetails"
}
function Write-Log {
Param (
[Parameter(Mandatory = $false, Position = 0)]
[ValidateSet("Info", "Warning", "Error")]
$Category = 'Info',
[Parameter(Mandatory = $true, Position = 1)]
$Message
)
$Date = get-date
$Content = "[$Date]`t$Category`t`t$Message`n"
Add-Content $Script:Log $content -ErrorAction Stop
If ($Verbose) {
Write-Verbose $Content
}
Else {
Switch ($Category) {
'Info' { Write-Host $content }
'Error' { Write-Error $Content }
'Warning' { Write-Warning $Content }
}
}
}
function Get-WebFile {
param(
[parameter(Mandatory)]
[string]$FileName,
[parameter(Mandatory)]
[string]$URL
)
$Counter = 0
do {
Invoke-WebRequest -Uri $URL -OutFile $FileName -ErrorAction 'SilentlyContinue'
if ($Counter -gt 0) {
Start-Sleep -Seconds 30
}
$Counter++
}
until((Test-Path $FileName) -or $Counter -eq 9)
}
Function Set-RegistryValue {
[CmdletBinding()]
param (
[Parameter()]
[string]
$Name,
[Parameter()]
[string]
$Path,
[Parameter()]
[string]$PropertyType,
[Parameter()]
$Value
)
Begin {
Write-Log -message "[Set-RegistryValue]: Setting Registry Value: $Name"
}
Process {
# Create the registry Key(s) if necessary.
If (!(Test-Path -Path $Path)) {
Write-Log -message "[Set-RegistryValue]: Creating Registry Key: $Path"
New-Item -Path $Path -Force | Out-Null
}
# Check for existing registry setting
$RemoteValue = Get-ItemProperty -Path $Path -Name $Name -ErrorAction SilentlyContinue
If ($RemoteValue) {
# Get current Value
$CurrentValue = Get-ItemPropertyValue -Path $Path -Name $Name
Write-Log -message "[Set-RegistryValue]: Current Value of $($Path)\$($Name) : $CurrentValue"
If ($Value -ne $CurrentValue) {
Write-Log -message "[Set-RegistryValue]: Setting Value of $($Path)\$($Name) : $Value"
Set-ItemProperty -Path $Path -Name $Name -Value $Value -Force | Out-Null
}
Else {
Write-Log -message "[Set-RegistryValue]: Value of $($Path)\$($Name) is already set to $Value"
}
}
Else {
Write-Log -message "[Set-RegistryValue]: Setting Value of $($Path)\$($Name) : $Value"
New-ItemProperty -Path $Path -Name $Name -PropertyType $PropertyType -Value $Value -Force | Out-Null
}
Start-Sleep -Milliseconds 500
}
End {
}
}
$ErrorActionPreference = 'Stop'
$Script:Name = 'Set-SessionHostConfiguration'
New-Log -Path (Join-Path -Path $env:SystemRoot -ChildPath 'Logs')
try {
##############################################################
# Add Recommended AVD Settings
##############################################################
$Settings = @(
# Disable Automatic Updates: https://docs.microsoft.com/en-us/azure/virtual-desktop/set-up-customize-master-image#disable-automatic-updates
[PSCustomObject]@{
Name = 'NoAutoUpdate'
Path = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU'
PropertyType = 'DWord'
Value = 1
},
# Enable Time Zone Redirection: https://docs.microsoft.com/en-us/azure/virtual-desktop/set-up-customize-master-image#set-up-time-zone-redirection
[PSCustomObject]@{
Name = 'fEnableTimeZoneRedirection'
Path = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services'
PropertyType = 'DWord'
Value = 1
}
)
##############################################################
# Add GPU Settings
##############################################################
# This setting applies to the VM Size's recommended for AVD with a GPU
if ($AmdVmSize -eq 'true' -or $NvidiaVmSize -eq 'true') {
$Settings += @(
# Configure GPU-accelerated app rendering: https://docs.microsoft.com/en-us/azure/virtual-desktop/configure-vm-gpu#configure-gpu-accelerated-app-rendering
[PSCustomObject]@{
Name = 'bEnumerateHWBeforeSW'
Path = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services'
PropertyType = 'DWord'
Value = 1
},
# Configure fullscreen video encoding: https://docs.microsoft.com/en-us/azure/virtual-desktop/configure-vm-gpu#configure-fullscreen-video-encoding
[PSCustomObject]@{
Name = 'AVC444ModePreferred'
Path = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services'
PropertyType = 'DWord'
Value = 1
},
[PSCustomObject]@{
Name = 'KeepAliveEnable'
Path = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services'
PropertyType = 'DWord'
Value = 1
},
[PSCustomObject]@{
Name = 'KeepAliveInterval'
Path = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services'
PropertyType = 'DWord'
Value = 1
},
[PSCustomObject]@{
Name = 'MinEncryptionLevel'
Path = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services'
PropertyType = 'DWord'
Value = 3
},
[PSCustomObject]@{
Name = 'AVCHardwareEncodePreferred'
Path = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services'
PropertyType = 'DWord'
Value = 1
}
)
}
# This setting applies only to VM Size's recommended for AVD with a Nvidia GPU
if ($NvidiaVmSize -eq 'true') {
$Settings += @(
# Configure GPU-accelerated frame encoding: https://docs.microsoft.com/en-us/azure/virtual-desktop/configure-vm-gpu#configure-gpu-accelerated-frame-encoding
[PSCustomObject]@{
Name = 'AVChardwareEncodePreferred'
Path = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services'
PropertyType = 'DWord'
Value = 1
}
)
}
# ##############################################################
# # Add Screen Capture Protection Setting
# ##############################################################
# if ($ScreenCaptureProtection -eq 'true') {
# $Settings += @(
# # Enable Screen Capture Protection: https://docs.microsoft.com/en-us/azure/virtual-desktop/screen-capture-protection
# [PSCustomObject]@{
# Name = 'fEnableScreenCaptureProtect'
# Path = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services'
# PropertyType = 'DWord'
# Value = 1
# }
# )
# }
##############################################################
# Add Fslogix Settings
##############################################################
if ($Fslogix -eq 'true') {
$FSLogixStorageFQDN = $FSLogixFileShare.Split('\')[2]
$Settings += @(
# Enables Fslogix profile containers: https://docs.microsoft.com/en-us/fslogix/profile-container-configuration-reference#enabled
[PSCustomObject]@{
Name = 'Enabled'
Path = 'HKLM:\SOFTWARE\Fslogix\Profiles'
PropertyType = 'DWord'
Value = 1
},
# Deletes a local profile if it exists and matches the profile being loaded from VHD: https://docs.microsoft.com/en-us/fslogix/profile-container-configuration-reference#deletelocalprofilewhenvhdshouldapply
[PSCustomObject]@{
Name = 'DeleteLocalProfileWhenVHDShouldApply'
Path = 'HKLM:\SOFTWARE\FSLogix\Profiles'
PropertyType = 'DWord'
Value = 1
},
# The folder created in the Fslogix fileshare will begin with the username instead of the SID: https://docs.microsoft.com/en-us/fslogix/profile-container-configuration-reference#flipflopprofiledirectoryname
[PSCustomObject]@{
Name = 'FlipFlopProfileDirectoryName'
Path = 'HKLM:\SOFTWARE\FSLogix\Profiles'
PropertyType = 'DWord'
Value = 1
},
# # Loads FRXShell if there's a failure attaching to, or using an existing profile VHD(X): https://docs.microsoft.com/en-us/fslogix/profile-container-configuration-reference#preventloginwithfailure
# [PSCustomObject]@{
# Name = 'PreventLoginWithFailure'
# Path = 'HKLM:\SOFTWARE\FSLogix\Profiles'
# PropertyType = 'DWord'
# Value = 1
# },
# # Loads FRXShell if it's determined a temp profile has been created: https://docs.microsoft.com/en-us/fslogix/profile-container-configuration-reference#preventloginwithtempprofile
# [PSCustomObject]@{
# Name = 'PreventLoginWithTempProfile'
# Path = 'HKLM:\SOFTWARE\FSLogix\Profiles'
# PropertyType = 'DWord'
# Value = 1
# },
# List of file system locations to search for the user's profile VHD(X) file: https://docs.microsoft.com/en-us/fslogix/profile-container-configuration-reference#vhdlocations
[PSCustomObject]@{
Name = 'VHDLocations'
Path = 'HKLM:\SOFTWARE\FSLogix\Profiles'
PropertyType = 'MultiString'
Value = $FSLogixFileShare
},
[PSCustomObject]@{
Name = 'VolumeType'
Path = 'HKLM:\SOFTWARE\FSLogix\Profiles'
PropertyType = 'String'
Value = 'vhdx'
},
[PSCustomObject]@{
Name = 'LockedRetryCount'
Path = 'HKLM:\SOFTWARE\FSLogix\Profiles'
PropertyType = 'DWord'
Value = 3
},
[PSCustomObject]@{
Name = 'LockedRetryInterval'
Path = 'HKLM:\SOFTWARE\FSLogix\Profiles'
PropertyType = 'DWord'
Value = 15
},
[PSCustomObject]@{
Name = 'ReAttachIntervalSeconds'
Path = 'HKLM:\SOFTWARE\FSLogix\Profiles'
PropertyType = 'DWord'
Value = 15
},
[PSCustomObject]@{
Name = 'ReAttachRetryCount'
Path = 'HKLM:\SOFTWARE\FSLogix\Profiles'
PropertyType = 'DWord'
Value = 3
}
)
if ($IdentityServiceProvider -eq "EntraIDKerberos" -and $Fslogix -eq 'true') {
$Settings += @(
[PSCustomObject]@{
Name = 'CloudKerberosTicketRetrievalEnabled'
Path = 'HKLM:\SYSTEM\CurrentControlSet\Control\Lsa\Kerberos\Parameters'
PropertyType = 'DWord'
Value = 1
},
[PSCustomObject]@{
Name = 'LoadCredKeyFromProfile'
Path = 'HKLM:\Software\Policies\Microsoft\AzureADAccount'
PropertyType = 'DWord'
Value = 1
},
[PSCustomObject]@{
Name = $IdentityDomainName
Path = 'HKLM:\SYSTEM\CurrentControlSet\Control\Lsa\Kerberos\domain_realm'
PropertyType = 'String'
Value = $FSLogixStorageFQDN
}
)
}
If ($FsLogixStorageAccountKey -ne '') {
$SAName = $FSLogixStorageFQDN.Split('.')[0]
Write-Log -Message "Adding Local Storage Account Key for '$FSLogixStorageFQDN' to Credential Manager" -Category 'Info'
$CMDKey = Start-Process -FilePath 'cmdkey.exe' -ArgumentList "/add:$FSLogixStorageFQDN /user:localhost\$SAName /pass:$FSLogixStorageAccountKey" -Wait -PassThru
If ($CMDKey.ExitCode -ne 0) {
Write-Log -Message "CMDKey Failed with '$($CMDKey.ExitCode)'. Failed to add Local Storage Account Key for '$FSLogixStorageFQDN' to Credential Manager" -Category 'Error'
}
Else {
Write-Log -Message "Successfully added Local Storage Account Key for '$FSLogixStorageFQDN' to Credential Manager" -Category 'Info'
}
$Settings += @(
# Attach the users VHD(x) as the computer: https://learn.microsoft.com/en-us/fslogix/reference-configuration-settings?tabs=profiles#accessnetworkascomputerobject
[PSCustomObject]@{
Name = 'AccessNetworkAsComputerObject'
Path = 'HKLM:\SOFTWARE\FSLogix\Profiles'
PropertyType = 'DWord'
Value = 1
}
)
$Settings += @(
# Disable Roaming the Recycle Bin because it corrupts. https://learn.microsoft.com/en-us/fslogix/reference-configuration-settings?tabs=profiles#roamrecyclebin
[PSCustomObject]@{
Name = 'RoamRecycleBin'
Path = 'HKLM:\SOFTWARE\FSLogix\Apps'
PropertyType = 'DWord'
Value = 0
}
)
# Disable the Recycle Bin
Reg LOAD "HKLM\TempHive" "$env:SystemDrive\Users\Default User\NtUser.dat"
Set-RegistryValue -Path 'HKLM:\TempHive\Software\Microsoft\Windows\CurrentVersion\Policies\Explorer' -Name NoRecycleFiles -PropertyType DWord -Value 1
Write-Log -Message "Unloading default user hive."
$null = cmd /c REG UNLOAD "HKLM\TempHive" '2>&1'
If ($LastExitCode -ne 0) {
# sometimes the registry doesn't unload properly so we have to perform powershell garbage collection first.
[GC]::Collect()
[GC]::WaitForPendingFinalizers()
Start-Sleep -Seconds 5
$null = cmd /c REG UNLOAD "HKLM\TempHive" '2>&1'
If ($LastExitCode -eq 0) {
Write-Log -Message "Hive unloaded successfully."
}
Else {
Write-Log -category Error -Message "Default User hive unloaded with exit code [$LastExitCode]."
}
}
Else {
Write-Log -Message "Hive unloaded successfully."
}
}
$LocalAdministrator = (Get-LocalUser | Where-Object { $_.SID -like '*-500' }).Name
$LocalGroups = 'FSLogix Profile Exclude List', 'FSLogix ODFC Exclude List'
ForEach ($Group in $LocalGroups) {
If (-not (Get-LocalGroupMember -Group $Group | Where-Object { $_.Name -like "*$LocalAdministrator" })) {
Add-LocalGroupMember -Group $Group -Member $LocalAdministrator
}
}
}
##############################################################
# Add Microsoft Entra ID Join Setting
##############################################################
if ($IdentityServiceProvider -match "EntraID") {
$Settings += @(
# Enable PKU2U: https://docs.microsoft.com/en-us/azure/virtual-desktop/troubleshoot-azure-ad-connections#windows-desktop-client
[PSCustomObject]@{
Name = 'AllowOnlineID'
Path = 'HKLM:\SYSTEM\CurrentControlSet\Control\Lsa\pku2u'
PropertyType = 'DWord'
Value = 1
}
)
}
# Set registry settings
foreach ($Setting in $Settings) {
Set-RegistryValue -Name $Setting.Name -Path $Setting.Path -PropertyType $Setting.PropertyType -Value $Setting.Value -Verbose
}
# Resize OS Disk
if ($ExtendOsDisk -eq 'true') {
Write-Log -message "Resizing OS Disk"
$driveLetter = $env:SystemDrive.Substring(0, 1)
$size = Get-PartitionSupportedSize -DriveLetter $driveLetter
Resize-Partition -DriveLetter $driveLetter -Size $size.SizeMax
Write-Log -message "OS Disk Resized"
}
##############################################################
# Add Defender Exclusions for FSLogix
##############################################################
# https://docs.microsoft.com/en-us/azure/architecture/example-scenario/wvd/windows-virtual-desktop-fslogix#antivirus-exclusions
if ($Fslogix -eq 'true') {
$Files = @(
"%ProgramFiles%\FSLogix\Apps\frxdrv.sys",
"%ProgramFiles%\FSLogix\Apps\frxdrvvt.sys",
"%ProgramFiles%\FSLogix\Apps\frxccd.sys",
"%TEMP%\*.VHD",
"%TEMP%\*.VHDX",
"%Windir%\TEMP\*.VHD",
"%Windir%\TEMP\*.VHDX"
"$FslogixFileShareName\*.VHD"
"$FslogixFileShareName\*.VHDX"
)
foreach ($File in $Files) {
Add-MpPreference -ExclusionPath $File
}
Write-Log -Message 'Enabled Defender exlusions for FSLogix paths' -Category 'Info'
$Processes = @(
"%ProgramFiles%\FSLogix\Apps\frxccd.exe",
"%ProgramFiles%\FSLogix\Apps\frxccds.exe",
"%ProgramFiles%\FSLogix\Apps\frxsvc.exe"
)
foreach ($Process in $Processes) {
Add-MpPreference -ExclusionProcess $Process
}
Write-Log -Message 'Enabled Defender exlusions for FSLogix processes' -Category 'Info'
}
##############################################################
# Install the AVD Agent
##############################################################
$BootInstaller = 'AVD-Bootloader.msi'
Get-WebFile -FileName $BootInstaller -URL 'https://query.prod.cms.rt.microsoft.com/cms/api/am/binary/RWrxrH'
Start-Process -FilePath 'msiexec.exe' -ArgumentList "/i $BootInstaller /quiet /qn /norestart /passive" -Wait -Passthru
Write-Log -Message 'Installed AVD Bootloader' -Category 'Info'
Start-Sleep -Seconds 5
$AgentInstaller = 'AVD-Agent.msi'
Get-WebFile -FileName $AgentInstaller -URL 'https://query.prod.cms.rt.microsoft.com/cms/api/am/binary/RWrmXv'
Start-Process -FilePath 'msiexec.exe' -ArgumentList "/i $AgentInstaller /quiet /qn /norestart /passive REGISTRATIONTOKEN=$HostPoolRegistrationToken" -Wait -PassThru
Write-Log -Message 'Installed AVD Agent' -Category 'Info'
Start-Sleep -Seconds 5
##############################################################
# Restart VM
##############################################################
if ($IdentityServiceProvider -eq "EntraIDKerberos" -and $AmdVmSize -eq 'false' -and $NvidiaVmSize -eq 'false') {
Start-Process -FilePath 'shutdown' -ArgumentList '/r /t 30'
}
}
catch {
Write-Log -Message $_ -Category 'Error'
throw
}