FSLogix Profile Containers with Azure Files: The Complete Setup

Every AVD deployment I have picked up from another team has the same failure waiting in it. The images are clean, the host pool is sized properly, Conditional Access is doing its job, and then a user signs in to a different session host and finds an empty desktop. No Outlook profile, no OneDrive sync, no browser bookmarks. Someone built the environment without profile containers and nobody noticed until the pool scaled past one host.
That is the whole reason FSLogix exists. A session host is disposable. The user's profile cannot be. Profile containers move the profile into a VHDX on an SMB share, attach it at sign-in, and detach it at sign-out, so the user gets the same desktop no matter which host the broker sends them to.
This guide is the cloud-only version. No domain controllers, no file server VM, no hybrid sync. Azure Files with Microsoft Entra Kerberos, Entra-joined session hosts, and FSLogix configured through policy. It is the piece I promised at the end of the AVD image management guide, and it is the half that people most often get subtly wrong.
If you want the deployment side of this first, I built the whole serverless AVD environment on video:
Before You Start: Premium or Standard
The first decision costs you the most if you get it wrong, and it is the one people rush.
Profile containers are a latency workload, not a throughput workload. Sign-in performance is driven by IOPS, and standard file shares bill IOPS on a transaction model that gets expensive and unpredictable under a login storm. Premium file shares provision IOPS with the share size, which is what you actually want when two hundred people sign in between 8:55 and 9:05.
The rule I use: anything past a pilot goes premium. A 100 GB premium share gives you a baseline of around 100 IOPS with bursting, and you scale the share to buy IOPS rather than capacity. You will almost always be buying IOPS.
Warning: Premium file shares are provisioned, which means you pay for the size you set, not the size you use. Setting a 5 TB share "to be safe" is a bill you will notice. Start at what you need and grow it, because premium shares can be scaled up without downtime.
Prerequisites
- An Entra ID tenant with the AVD host pool already deployed
- Session hosts that are Microsoft Entra joined (not hybrid, not domain joined) running Windows 11 multi-session
- Azure CLI 2.60 or later, signed in with rights to create storage accounts and assign roles
- A security group containing your AVD users. I will call it
sg-avd-users
Set your variables. Everything below reuses them.
RG="rg-avd-prod"
LOCATION="canadacentral"
STORAGE="stavdprofiles$RANDOM"
SHARE="profiles"
USER_GROUP="sg-avd-users"
Step 1: Create the Storage Account and Share
Premium file storage is its own account kind. You cannot flip a standard account to premium later, so get this right at creation.
az storage account create \
--name $STORAGE \
--resource-group $RG \
--location $LOCATION \
--sku Premium_LRS \
--kind FileStorage \
--enable-large-file-share \
--https-only false \
--min-tls-version TLS1_2
Two flags there surprise people. --kind FileStorage is what makes it a premium file account. And --https-only false is required because SMB does not run over HTTPS. Leaving it true is the single most common reason a share refuses to mount with an unhelpful error.
Now create the share itself, sized in GB:
az storage share-rm create \
--resource-group $RG \
--storage-account $STORAGE \
--name $SHARE \
--quota 500 \
--enabled-protocols SMB
Pro tip: Size the share for IOPS, not for profiles. Take your expected concurrent sign-ins, budget roughly 10 IOPS per user during a login storm, and set the quota to at least that number in GB. A 500 GB share gives you a 500 IOPS baseline, which comfortably covers fifty concurrent sign-ins.
Step 2: Enable Microsoft Entra Kerberos
This is the switch that makes cloud-only work. It lets an Entra-joined machine get a Kerberos ticket for the storage account without a domain controller anywhere in the path.
az storage account update \
--name $STORAGE \
--resource-group $RG \
--enable-files-aadkerb true
There is a second half to this that the CLI does not do for you. Enabling Entra Kerberos creates a service principal for the storage account in your tenant, and that principal needs admin consent granted on its API permissions before tickets will issue. Go to the storage account, open File shares, then Active Directory, and grant consent. It is one click and everything downstream fails without it.
Gotcha: If
--enable-files-aadkerbfails withMicrosoftGraphRequestFailed, you do not have rights to create the service principal in the tenant. This is a directory permission problem, not a storage problem, and no amount of retrying the storage command will fix it.
Step 3: Set the Default Share Permission
Here is the step that separates a working cloud-only deployment from a broken one, and it is barely mentioned in most walkthroughs.
With hybrid identities, per-user RBAC assignments on the share are enough. With cloud-only identities, they are not. Azure Files needs a default share permission on the storage account to act as the fallback authorization path for SMB, because the identity in the Kerberos ticket cannot be resolved back to a synced object.
az storage account update \
--name $STORAGE \
--resource-group $RG \
--default-share-permission StorageFileDataSmbShareContributor
Skip this and your users will authenticate successfully and then be denied access to the share, which produces the least helpful troubleshooting experience in the entire stack. Everything looks correct and nothing works.
Step 4: Assign RBAC on the Share
The default share permission is the fallback. You still want an explicit assignment so the intent is documented and so you can see it in an access review.
SHARE_SCOPE=$(az storage account show \
--name $STORAGE --resource-group $RG \
--query id -o tsv)/fileServices/default/fileshares/$SHARE
GROUP_ID=$(az ad group show --group "$USER_GROUP" --query id -o tsv)
az role assignment create \
--assignee $GROUP_ID \
--role "Storage File Data SMB Share Contributor" \
--scope $SHARE_SCOPE
Users get Contributor, not Elevated Contributor. Elevated Contributor grants NTFS ownership rights across the share, which means one user can read another user's profile container. I have found that assignment in production more than once, always applied by someone trying to fix a permissions error by escalating until it worked.
Warning: Never assign Storage File Data SMB Share Elevated Contributor to your user group. It defeats the NTFS isolation you are about to configure in the next step.
Step 5: Configure NTFS Permissions
Azure RBAC gets you to the share. NTFS decides what happens inside it. FSLogix needs each user to create their own folder and nobody else's.
Mount the share from an admin workstation using the storage account key. This is the one time you use the key, purely to set the ACLs.
$key = (az storage account keys list --account-name $env:STORAGE `
--resource-group $env:RG --query "[0].value" -o tsv)
net use Z: \\$env:STORAGE.file.core.windows.net\profiles `
/user:localhost\$env:STORAGE $key
Now set the permissions. The pattern is: break inheritance, give users the right to create folders but not traverse into each other's.
icacls Z: /inheritance:r
icacls Z: /grant "sg-avd-users:(M)"
icacls Z: /grant "Creator Owner:(OI)(CI)(IO)(F)"
icacls Z: /grant "BUILTIN\Administrators:(OI)(CI)(F)"
net use Z: /delete
Creator Owner with (OI)(CI)(IO) is what gives each user full control of the folder they create and nothing else. The (M) on the group is Modify at the root only, which is enough to create a folder and not enough to open somebody else's.
Step 6: Enable Cloud Kerberos Ticket Retrieval on the Hosts
Entra-joined machines do not fetch a cloud Kerberos TGT by default. You have to turn it on, and it is per host.
$path = "HKLM:\SYSTEM\CurrentControlSet\Control\Lsa\Kerberos\Parameters"
New-Item -Path $path -Force | Out-Null
New-ItemProperty -Path $path -Name "CloudKerberosTicketRetrievalEnabled" `
-Value 1 -PropertyType DWORD -Force
This is a reboot-required setting. If you are baking it into the image with Azure Image Builder, put it in a customizer step and it applies to every host you deploy from that image. If you are retrofitting an existing pool, push it with Intune as a settings catalog policy or a remediation script.
Pro tip: Bake this into the image. Retrofitting it to a running pool means a reboot cycle across every session host, which is a maintenance window you did not need to spend.
Step 7: Configure FSLogix
FSLogix reads its configuration from the registry. Two values are mandatory and everything else is tuning.
$fsl = "HKLM:\SOFTWARE\FSLogix\Profiles"
New-Item -Path $fsl -Force | Out-Null
Set-ItemProperty -Path $fsl -Name "Enabled" -Value 1 -Type DWord
Set-ItemProperty -Path $fsl -Name "VHDLocations" `
-Value "\\stavdprofiles.file.core.windows.net\profiles" -Type MultiString
# Tuning that matters in production
Set-ItemProperty -Path $fsl -Name "DeleteLocalProfileWhenVHDShouldApply" -Value 1 -Type DWord
Set-ItemProperty -Path $fsl -Name "FlipFlopProfileDirectoryName" -Value 1 -Type DWord
Set-ItemProperty -Path $fsl -Name "SizeInMBs" -Value 30000 -Type DWord
Set-ItemProperty -Path $fsl -Name "VolumeType" -Value "VHDX" -Type String
DeleteLocalProfileWhenVHDShouldApply is the one people leave out and then spend a week debugging. Without it, if a container fails to attach once, Windows creates a local profile, and from then on the local profile wins and the container never attaches again. The user's data quietly diverges on one host.
FlipFlopProfileDirectoryName puts the username first in the folder name instead of the SID, which turns an unreadable share into one you can actually troubleshoot.
SizeInMBs at 30000 gives a 30 GB dynamic VHDX. It only consumes what it uses, so this is a ceiling and not an allocation.
The Exclusions Everyone Misses
A profile container holds everything in the user profile by default, including things that should never be roamed. Teams caches, OneDrive's local sync database, and Windows Search indexes are the three that cause the most pain, because they are large, they change constantly, and they do not survive being moved between hosts.
Exclusions live in an XML file that FSLogix reads at attach time. Place it in the FSLogix install folder on the image.
<?xml version="1.0" encoding="UTF-8"?>
<FrxProfileFolderRedirection ExcludeCommonFolders="0">
`<Excludes>`
<Exclude Copy="0">AppData\Local\Temp`</Exclude>`
<Exclude Copy="0">AppData\Local\Microsoft\Windows\INetCache`</Exclude>`
<Exclude Copy="0">AppData\Local\Microsoft\Windows\WebCache`</Exclude>`
<Exclude Copy="0">AppData\Local\CrashDumps`</Exclude>`
<Exclude Copy="0">AppData\Local\Microsoft\Teams\Current`</Exclude>`
<Exclude Copy="0">AppData\Local\Microsoft\Teams\Packages`</Exclude>`
`</Excludes>`
`<Includes>`
`<Include>`AppData\Roaming\Microsoft\Teams`</Include>`
`</Includes>`
`</FrxProfileFolderRedirection>`
Note the shape of the Teams entries. You exclude the local cache and binaries, and you explicitly include the roaming folder that holds the actual user state. Excluding all of Teams is a common overcorrection that logs the user out of Teams on every single sign-in.
Gotcha: Exclusions are evaluated at container attach. Adding an exclusion does not shrink an existing container, because the data is already inside it. Existing profiles keep their bloat until the container is recreated.
What Will Bite You
The container is attached but the desktop is still empty. Almost always the local profile problem from Step 7. Check C:\Users on the host: if there is a real folder for the user rather than a junction into the container, Windows is serving a local profile. Set DeleteLocalProfileWhenVHDShouldApply and clear the stale profiles.
Sign-in takes ninety seconds. Check share IOPS before you look at anything else. A premium share that is undersized behaves exactly like a broken configuration, because every attach is queueing. In the metrics blade, look at Transactions and SuccessE2ELatency on the file service during your morning peak.
One user cannot sign in, everyone else is fine. That is a locked container, not a config problem. FSLogix takes an exclusive lock on the VHDX, and an ungraceful host shutdown can leave the lock in place. The .lock and .meta files sit next to the VHDX on the share and can be removed once you have confirmed the session is genuinely gone.
Everything worked, then you added a second storage account. VHDLocations accepts multiple paths and FSLogix will use the first one that responds. That is a failover feature, not a load-balancing feature, and people frequently misread it as the latter and wonder why the second account sits idle.
Verify It Actually Works
Do not trust a clean sign-in as proof. Sign in, create a file on the desktop, sign out fully, and sign in again while forcing a different host.
# On the session host, confirm the container attached
Get-Volume | Where-Object { $_.FileSystemLabel -like "*Profile*" }
# Check the FSLogix event log for the attach
Get-WinEvent -LogName "Microsoft-FSLogix-Apps/Operational" -MaxEvents 20 |
Select-Object TimeCreated, Id, Message | Format-List
Event ID 26 is a successful attach. If you see 31 or 33, the container failed to attach and Windows fell back, which is precisely the state that quietly creates local profiles.
Conclusions
Profile containers are not complicated, but they have three places where a wrong setting produces a silent failure rather than an error: the default share permission, the local profile fallback, and the Teams exclusions. Get those three right and the rest is mechanical.
The cloud-only path is worth the setup. No domain controllers to patch, no line of sight requirements, no hybrid sync to break at the worst possible moment. For a greenfield AVD build in 2026, there is very little reason to do it any other way.
What's Next
- App attach to deliver applications without rebuilding the image every time a version changes
- Azure Monitor for AVD to watch profile attach times and catch an undersized share before your users do
- Scaling plans to shut hosts down outside business hours, which is where the AVD bill actually lives




