Month: March 2022

FSLogix Profile Container with Azure Files and AzureAD

I have already written a post about how to deploy AVD with AzureAD joined VMs
(https://tech-guys.blog/2021/11/10/azure-virtual-desktop-and-azuread-joined-vm/)

Addition to AzureAD joined VMs we need an Azure Storage Account, an Azure File share as well to save our FSLogix Profiles. This post will guide you to all steps.

At time of writing AzureFiles and AzureAD Kerberos functionality is still in preview (https://docs.microsoft.com/en-us/azure/virtual-desktop/create-profile-container-azure-ad)

Requirements:

  • User must be a hybrid user identities!!!!!!
  1. you must have an Azure Subscription and ADConnect already installed and configured
  2. create a storage account
    I used the following settings and leaved all other settings by default…

3. create azure file share

for my lab I only use SKU standard but for prod environments I always use and recommend premium storage. Microsoft created insights on sizing and designing fslogix solutions for enterprises here: https://docs.microsoft.com/en-us/azure/architecture/example-scenario/wvd/windows-virtual-desktop-fslogix

4. enable Azure AD authorization

Connect-AzAccount -Tenant $tenantId -SubscriptionId $subscriptionId
$Uri = ('https://management.azure.com/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Storage/storageAccounts/{2}?api-version=2021-04-01' -f $subscriptionId, $resourceGroupName, $storageAccountName);
$json = @{properties=@{azureFilesIdentityBasedAuthentication=@{directoryServiceOptions="AADKERB"}}};
$json = $json | ConvertTo-Json -Depth 99
$token = $(Get-AzAccessToken).Token
$headers = @{ Authorization="Bearer $token" }
try {
Invoke-RestMethod -Uri $Uri -ContentType 'application/json' -Method PATCH -Headers $Headers -Body $json;
} catch {
Write-Host $_.Exception.ToString()
Write-Error -Message "Caught exception setting Storage Account directoryServiceOptions=AADKERB: $_" -ErrorAction Stop
}
New-AzStorageAccountKey -ResourceGroupName $resourceGroupName -Name $storageAccountName -KeyName kerb1 -ErrorAction Stop

5. create an Azure AD application that represent the storage account (during preview only via Powershell)

Install-Module -Name Az.StorageInstall-Module -Name AzureAD
$tenantid = "yourid"
$subscriptionid = "yourid"
$resourceGroupName = "rg"
$storageAccountName = "name"
#set password between storage account and azuread
$kerbKey1 = Get-AzStorageAccountKey -ResourceGroupName $resourceGroupName -Name $storageAccountName -ListKerbKey | Where-Object { $_.KeyName -like "kerb1" }
$aadPasswordBuffer = [System.Linq.Enumerable]::Take([System.Convert]::FromBase64String($kerbKey1.Value), 32);
$password = "kk:" + [System.Convert]::ToBase64String($aadPasswordBuffer);
#connect to tennant

Connect-AzureAD
Set-AzContext -Subscription $subscriptionid
$azureAdTenantDetail = Get-AzureADTenantDetail;
$azureAdTenantId = $azureAdTenantDetail.ObjectId
$azureAdPrimaryDomain = ($azureAdTenantDetail.VerifiedDomains | Where-Object {$_._Default -eq $true}).Name

#generate service principal names

$servicePrincipalNames = New-Object string[] 3
$servicePrincipalNames[0] = 'HTTP/{0}.file.core.windows.net' -f $storageAccountName
$servicePrincipalNames[1] = 'CIFS/{0}.file.core.windows.net' -f $storageAccountName
$servicePrincipalNames[2] = 'HOST/{0}.file.core.windows.net' -f $storageAccountName

#create app

$application = New-AzureADApplication -DisplayName $storageAccountName -IdentifierUris $servicePrincipalNames -GroupMembershipClaims "All";

#create service

$servicePrincipal = New-AzureADServicePrincipal -AccountEnabled $true -AppId $application.AppId -ServicePrincipalType "Application";

$Token = ([Microsoft.Open.Azure.AD.CommonLibrary.AzureSession]::AccessTokens['AccessToken']).AccessToken
$Uri = ('https://graph.windows.net/{0}/{1}/{2}?api-version=1.6' -f $azureAdPrimaryDomain, 'servicePrincipals', $servicePrincipal.ObjectId)
$json = @'
{
"passwordCredentials": [
{
"customKeyIdentifier": null,
"endDate": "",
"value": "",
"startDate": ""
}]
}
'@
$now = [DateTime]::UtcNow
$json = $json -replace "", $now.AddDays(-1).ToString("s")
$json = $json -replace "", $now.AddMonths(6).ToString("s")
$json = $json -replace "", $password
$Headers = @{'authorization' = "Bearer $($Token)"}
try {
Invoke-RestMethod -Uri $Uri -ContentType 'application/json' -Method Patch -Headers $Headers -Body $json
Write-Host "Success: Password is set for $storageAccountName"
} catch {
Write-Host $_.Exception.ToString()
Write-Host "StatusCode: " $_.Exception.Response.StatusCode.value
Write-Host "StatusDescription: " $_.Exception.Response.StatusDescription
}

Result is an app with the name of our storage account:

and you can see on your storage account a hint that kerberos is enabled.

6. API Permissions

now you can assign API permissions to that app:

1st we need openid permissions

  • openid – Sign user in
  • profile – View users basic profile

2nd we need User permissions

  • User.Read – Sign in and read user profile

after adding it is important to click “Grant admin consent”. For this you need right permissions within AzureAD.

7. Assign SMB Permissions

To allow an User to create profile container we need to assign RBAC Roles to the Storage account.

Storage File Data SMB Share Contributor for all AVD Users and “Elevated Contributor” to a Group or Users that need Admin Permission.

8. Assign directory level access permissions

For me it was quiet confusing where I have to set these permisions and which system I have to use. In my Lab I have one Windows VM with AD DS and ADConnect installed that I used to run that scripts.

From MS Documentation I found:

The system you use to configure the permissions must meet the following requirements:

  • The version of Windows meets the supported OS requirements defined in the Prerequisites section.
  • Is Azure AD-joined or Hybrid Azure AD-joined to the same Azure AD tenant as the storage account.
  • Has line-of-sight to the domain controller.
  • Is domain-joined to your Active Directory (Windows Explorer method only).
Connect-AzAccount -Tenant $tenantId -SubscriptionId $subscriptionId
$AdModule = Get-Module ActiveDirectory;
if ($null -eq $AdModule) {
Write-Error "Please install and/or import the ActiveDirectory PowerShell module." -ErrorAction Stop;
}
$domainInformation = Get-ADDomain
$Domain = $domainInformation.DnsRoot
$domainGuid = $domainInformation.ObjectGUID.ToString()
$domainName = $domainInformation.DnsRoot
$domainSid = $domainInformation.DomainSID.Value
$forestName = $domainInformation.Forest
$netBiosDomainName = $domainInformation.DnsRoot
$azureStorageSid = $domainSid + "-123454321";
Write-Verbose "Setting AD properties on $storageAccountName in $resourceGroupName : EnableActiveDirectoryDomainServicesForFile=$true, ActiveDirectoryDomainName=$domainName,
ActiveDirectoryNetBiosDomainName=$netBiosDomainName, ActiveDirectoryForestName=$($domainInformation.Forest) ActiveDirectoryDomainGuid=$domainGuid, ActiveDirectoryDomainSid=$domainSid,
ActiveDirectoryAzureStorageSid=$azureStorageSid"
$Uri = ('https://management.azure.com/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Storage/storageAccounts/{2}?api-version=2021-04-01' -f $subscriptionId, $resourceGroupName, $storageAccountName);
$json=
@{
properties=
@{azureFilesIdentityBasedAuthentication=
@{directoryServiceOptions="AADKERB";
activeDirectoryProperties=@{domainName="$($domainName)";
netBiosDomainName="$($netBiosDomainName)";
forestName="$($forestName)";
domainGuid="$($domainGuid)";
domainSid="$($domainSid)";
azureStorageSid="$($azureStorageSid)"}
}
}
};
$json = $json | ConvertTo-Json -Depth 99
$token = $(Get-AzAccessToken).Token
$headers = @{ Authorization="Bearer $token" }
try {
Invoke-RestMethod -Uri $Uri -ContentType 'application/json' -Method PATCH -Headers $Headers -Body $json
} catch {
Write-Host $_.Exception.ToString()
Write-Host "Error setting Storage Account AD properties. StatusCode:" $_.Exception.Response.StatusCode.value__
Write-Host "Error setting Storage Account AD properties. StatusDescription:" $_.Exception.Response.StatusDescription
Write-Error -Message "Caught exception setting Storage Account AD properties: $_" -ErrorAction Stop
}

9. Enable AzureAD functionality via Registry

reg add HKLM\SYSTEM\CurrentControlSet\Control\Lsa\Kerberos\Parameters /v CloudKerberosTicketRetrievalEnabled /t REG_DWORD /d 1

10. Configure SessionHost

On my Sessionhost I configured FSLogix like this:

Because I used that host already with my test account I already had a local profile. Within my first test it was not working, but with allowing FSLogix to delete lcoal profile than I was able to login and my profile container was created.

To be honest I had some trouble to get this working. Maybe it was one of my reboot to solve this 😉

Thanks for reading

AVD “Start on Connect”

When a Virtual Machine is running we have to pay for using CPU and RAM. When we are able to turn off (deallocate) a Virtual Machine then we can save costs.

With “Start on Connect” feature we can allow end users to turn on AVD Hosts and if they log off we can deallocate our hosts again. The result can be that we save costs!

I will go through all task to enable that feature.

1. Create and Assign Custom Role

First of all we need to have a custom role that will be used to start our hosts.

Subscription > Access control > add > custom role

Now we can create a role assignment. I did this on my Ressource Group “rg-avd” where my host is located.

our next step is to look for our new created custom role. if you can not find this, please try to refresh your session.

next part is to define members – here we need to find “Windows Virtual Desktop”

If you can not see any member then your user must be assigned to the security administrator role.

2. Enable “Start on Connect” on Hostpool

Let’s try if its working. My pool only one host and this host is deallocated.

Lets start our SessionDesktop:

Our Client will wait until one host is up and connected to the avd service.

Within the activity logs we can check who initiated the start of my host.

It was initiated by “Azure Virtual Desktop” as designed .

Thats it and thx for reading

Powered by WordPress & Theme by Anders Norén