Azure Files with Entra ID Join for AVD FSLogix Using Secure Variables and Powershell Credential Manager

This is a method to use Azure Files with Entra ID Joined session hosts for FSLogix. Credit to Tony Cai, Marcel Meurer, and Dave Stephenson. This is largely based on Tony Cai's previous article on the subject with the added benefit of using Nerdio secure variables and powershell secure strings.

 

In Nerdio:

Create a new Storage Account:

Apply the following settings:

  • Storage account: "unique storage account name here"
  • Resource Group: "resource group name here"
  • Location: "azure region here"
  • Performance: Premium
  • File Share name: "any name here"
  • Provisioned capacity: 100GB minimum
  •     Keep in mind the default fslogix profile size is set to 10GB per user so plan your capacity accordingly. Also, remember you pay for the size of the storage account and not for the amount of used space so don't oversize either.
  • Uncheck "Join to AD", this is because we are using a workaround to access the fileshare since they don't support cloud only identity authentication to azure files
  • Don't worry about any other settings, they aren't necessary for this workaround

In Azure:

Find your storage account you just created and open it, then select access keys on the left. You'll see the following screen.

Make sure to note down your storage account name and key by pressing show then copy the key.

 

Back in Nerdio:

Step 1:

Create a new Scripted Action and set Script Execution Method as "Individual" and name the script "Entra ID Joined Azure Files for FSLgx".

 

For the script copy the following:

-----------------------------------------------------------------------------------------------------------------------------------------

# Set Variables
# Requires the Secure Variables to be added: FSLgxStorageAccount and FSLgxSecret
$storageAccount = "$($SecureVars.FSLgxStorageAccount)"
$secret = "$($SecureVars.FSLgxSecret)" | ConvertTo-SecureString -AsPlainText -Force

# Adds credentials to Windows Credential Manager for Azure File Share
Function Add-FileServerCredential
{
    param(
        [string] $storageAccount,
        [securestring] $secret
    )

    # Create credentials from passed in Secure creds
    $fileServer = "$storageAccount.file.core.windows.net"
    $username = "localhost\$storageAccount"

    # Create credential object with storageAccountName as username and secret as password
    $credential = New-Object -TypeName PSCredential -ArgumentList $username, $secret

    $windowsCredential = New-StoredCredential -Target $fileServer -Credential $credential -Persist LocalMachine -Type DomainPassword
    if ($null -eq $windowsCredential)
    {
        throw "Error adding credential to Windows Credential Manager:`n $_"
    }
    else
    {
        Write-Host "Credentials successfully added to Windows Credential Manager."
    }
}

# Creates Registry Key LoadCredKeyFromProfile
Function Add-RegKey
{
    # Create Registry Path

    # Specify the registry path
    $regKeyPath = "HKLM:\Software\Policies\Microsoft\AzureADAccount"

    # Check if the registry path exists, and create it if not
    if (-not (Test-Path $regKeyPath))
    {
        New-Item -Path $regKeyPath -Force
    }

    # Create Registry Key
    $regKeyValue = "loadCredKeyFromProfile"
    $regKeyType = "DWORD"
    $regKeyData = 1
    New-ItemProperty -Path $regKeyPath -Name $regKeyValue -Value $regKeyData -PropertyType $regKeyType -Force | Out-Null

    #Check if key was created
    if (Test-Path $regKeyPath)
    {
        # Check if the registry value exists
        $regValue = Get-ItemProperty -Path $regKeyPath -Name $regKeyValue -ErrorAction SilentlyContinue
        if ($null -eq $regValue)
        {
            throw "Registry key '$regKeyValue' exists, but the value is not set."
        }
        else
        {
            Write-Host "Registry key successfully created."
        }
    }
    else
    {
        throw "Registry key does not exist."
    }
}

Function Main
{
    param(
        [string] $storageAccount,
        [securestring] $secret
    )

    Add-FileServerCredential -storageAccount $storageAccount -secret $secret
    Add-RegKey
}

Main -storageAccount $storageAccount -secret $secret

-----------------------------------------------------------------------------------------------------------------------------------------

One caveat with this script, make sure on your base image to install powershell credential manager using the following command: "install-module -name CredentialManager".

I tried bundling this into the same script but was running into issues installing the module on vm creation. If you have luck please post in the comments.

Step 2:

In your host pool properties select FSLogix on the left:

For FSLogix Profiles path enter the path:

  • \\[[STORAGEACCOUNTFQDN]\[FILESHARE]
  • Ex: \\test01182024.file.core.windows.net\fslogix

Next, add the following registry key:

  • AccessNetworkAsComputerObject
  • Vaue: 1

You can find this by selecting FSLogix Registry Options, then All Settings and filter it by name.

 

Now, select VM Deployments on the left side

Set your script to deploy on VM Creation and VM Started. This way the credential is sure to stick in Windows Credential Manager and not expire.

 

Last part, we need to add the secure variables in Nerdio.

Go to settings inside the account you are using the script on, then select Portal, then add a new variable. Use the following settings:

  • Name: FSLgxStorageAccount
  • Value: "storage account name you noted down earlier"
  • Secure vs Inherited: Secure
  • Windows Scripts: "Entra ID Joined Azure Files for FSLgx"
  • Azure Runbooks: Deselect "All Scripts"

Add a second variable, and use the following settings:

  • Name: FSLgxSecret
  • Value: "Storage account access key you noted down earlier"
  • Secure vs Inherited: Secure
  • Windows Scripts: "Entra ID Joined Azure Files for FSLgx"
  • Azure Runbooks: Deselect "All Scripts"
1

Comments (8 comments)

0
Avatar
DStephenson

Very cool, Kris Pennington! (and welcome to the community 🙂)

For the "credential manager" issue, could you do that as a separate scripted action and then add it and the "Entra ID Joined Azure Files for FSLgx" scripted action to a scripted action group and call that scripted action group during the VM creation?

Overview of Scripted Actions Groups – Nerdio Help Center

1
Avatar
Kris Pennington

I'm fairly certain that you would still run into the same issue doing it that way. I haven't tested that to confirm, but from when I was testing trying to install the powershell module credentialmanager during vm creation I was getting error's that I couldn't resolve until I waited after the vm was already created. It just seemed easier to install the module on the image and re-image my hosts than to troubleshoot the module installation. There's no reason you couldn't create a separate scripted action to install that module and run that on the image to make it easier though.

0
Avatar
Michal Szarecki

Hi Kris,
Is this working well for you?
This script is not adding credentials for me in Windows 11, or I can't see them.
Script works fine when I login as local admin and run it with variables filled in.

But when executed by Nerdio I'm getting below and credentials are not there, regardless of the message below

Script reference: https://cssa941f53e4bf3ab9b4d06f.blob.core.windows.net/custom-scripts/c9cfe393cf6424442bdbb40cce25a5a2.ps1
Pass variables: FSLgxStorageAccount, FSLgxSecret
Pass environment variables: TenantDomain:domain.onmicrosoft.com, DefaultDomain:, TenantId:TenantId, SubscriptionId:SubId, CustomerName:domain.com
Remove old extension, old result: {"name":"vm-avdh-cg-ae2d-run-script-ext-c9cfe393cf6424442bdbb40cce25a5a2.ps1","type":"Microsoft.Compute.CustomScriptExtension","typeHandlerVersion":"1.9.5","substatuses":[{"code":"ComponentStatus/StdOut/succeeded","level":"Info","displayStatus":"Provisioning succeeded","message":"","time":null},{"code":"ComponentStatus/StdErr/succeeded","level":"Info","displayStatus":"Provisioning succeeded","message":"Error adding credential to Windows Credential Manager:\r\n \r\nAt \r\nC:\\Packages\\Plugins\\Microsoft.Compute.CustomScriptExtension\\1.9.5\\Downloads\\0\\c9cfe393cf6424442bdbb40cce25a5a2.ps1:59 \r\nchar:9\r\n+ throw \"Error adding credential to Windows Credential Manager: ...\r\n+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\r\n + CategoryInfo : OperationStopped: (Error adding cr...tial Manager:\r\n :String) [], RuntimeException\r\n + FullyQualifiedErrorId : Error adding credential to Windows Credential Manager:\r\n \r\n \r\n","time":null}],"statuses":[{"code":"ProvisioningState/failed/1","level":"Error","displayStatus":"Provisioning failed","message":"Finished executing command","time":null}]}
Extension was removed
Script output:
VERBOSE: Writing credential to Windows Credential Manager
Credentials successfully added to Windows Credential Manager.
Registry key successfully created.
Extension added successfully

0
Avatar
Kris Pennington

Yes, this is currently still working form me. Usually, when I would get that error it has something to do with the way Nerdio is passing the variable to your script. I would run a basic debug script just passing those variables and outputting to console to see if Nerdio is passing them correctly or not.

0
Avatar
Ryan Dorman

This is much more comprehensive than my solution but here's what I came up with:

Was a bit of a challenges due to escaping characters and stringing everything together in a way PowerShell liked.

$arguments = '/add:'+$SecureVars.profileAccountName+'.file.core.windows.net'+' /user:localhost\'+$SecureVars.profileAccountName+' /pass:'+$Securevars.profileAccountSASkey
start-process cmdkey.exe -ArgumentList $arguments

1
Avatar
Marco Menezes

I tested the following on Azure Marketplace Windows 11 24H2, and it works like a charm. It does use PSCredential to read Nerdio Secure Variables like Kris' script, but it includes the install of the PS module Credential manager - no need to pre-install it on the image: 

# Nerdio Secure Variables

$storageAccount = "$($SecureVars.FSLgxStorageAccount)"
$secret = "$($SecureVars.FSLgxSecret)" | ConvertTo-SecureString -AsPlainText -Force

Set-ExecutionPolicy Bypass -Scope Process -Force

# Check if NuGet is installed
$nugetProvider = Get-PackageProvider -Name NuGet -Force -ErrorAction SilentlyContinue

if ($nugetProvider -and ($nugetProvider.Version -ge $requiredVersion)) {
   Write-Host "NuGet is already installed and meets the required version ($requiredVersion)."
} else {
   Write-Host "NuGet is missing or outdated. Installing/updating..."
   Install-PackageProvider -Name NuGet -MinimumVersion $requiredVersion -Force
   Write-Host "NuGet has been installed/updated to version $requiredVersion."
}

# Confirm installation
Get-PackageProvider -Name NuGet

# Define the repository name
$repoName = "PSGallery"  

# Get the current installation policy for the repository
$repo = Get-PSRepository -Name $repoName -Force -ErrorAction SilentlyContinue

if ($repo -and $repo.InstallationPolicy -eq "Trusted") {
   Write-Host "$repoName is already set to Trusted."
} else {
   Write-Host "$repoName is not set to Trusted. Updating now..."
   Set-PSRepository -Name $repoName -InstallationPolicy Trusted
   Write-Host "$repoName has been updated to Trusted."
}

# Verify the change
Get-PSRepository -Name $repoName

# Check if CredentialManager module is installed for all users
$moduleName = "CredentialManager"
$installedModule = Get-Module -Name $moduleName -ListAvailable

if ($installedModule) {
   Write-Host "$moduleName is already installed."
} else {
   Write-Host "$moduleName is not installed. Installing now..."
   Install-Module -Name $moduleName -Scope AllUsers -Force
   Write-Host "$moduleName has been installed."
}

# Import the module
Import-Module -Name $moduleName

# Verify successful import
Get-Module -Name $moduleName

##############################################################

# Loads variables
$fileServer = "$storageAccount.file.core.windows.net"
$username = "localhost\$storageAccount"

# Convert secure string into PSCredential object
$credential = New-Object -TypeName PSCredential -ArgumentList $username, $secret

# Store credentials using New-StoredCredential
$windowsCredential = New-StoredCredential -Target $fileServer -Credential $credential -Persist LocalMachine -Type DomainPassword

if ($null -eq $windowsCredential) {
   throw "Error adding credential to Windows Credential Manager:`n $_"
} else {
   Write-Host "Credentials successfully added to Windows Credential Manager."
}

# Check if the key exists
if (-not(Test-Path "HKLM:\Software\Policies\Microsoft\AzureADAccount")) {
# Create the key if it doesn't exist
   New-Item -Path "HKLM:\Software\Policies\Microsoft\AzureADAccount" -Force
  }
  
# Add or modify the property
New-ItemProperty -Path "HKLM:\Software\Policies\Microsoft\AzureADAccount" -Name "LoadCredKeyFromProfile" -Value 1 -Type DWord -Force

#Disable Credential Guard
New-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\Lsa" -Name "LsaCfgFlags" -Value 0 -force
 

 

1
Avatar
Dave Stephenson

This is amazing, Marco Menezes !!

It looks like we just need the two secure variables (FSLgxStorageAccount and FSLgxSecret) created at the account level and then apply the script on VM Creation? 
That's really slick!
Thanks again for sharing 🙂

1
Avatar
Marco Menezes

Thanks Dave Stephenson! You got it, two secure variables at account level and apply script on VM creation. No need to re-apply when VM is started.

Please sign in to leave a comment.