map Azure Files drive Entra Kerberosmap Azure Files drive Entra Kerberos

Mapping a network drive should be one of the easiest jobs in Windows administration. One net use command and you’re done.

Mapping an Azure Files share with Microsoft Entra Kerberos, silently, for every user, through Intune, with no window flashes and no manual fixes, was a very different story. It took me six versions of the same script.

Each version fixed one problem and exposed another. Some failures were obvious. Others were completely silent. At one point I blamed the wrong component, changed the script to fix it, and then had to undo that change when I found the real cause.

If you’re mapping Azure Files with Entra Kerberos and need the result to be reliable across a fleet of Windows devices, these are the problems I hit, how I diagnosed them and what I changed.

The goal and the design

The goal was a drive letter, Z:, pointing at an Azure Files share used as an archive. Users authenticate with Microsoft Entra Kerberos, so no storage account keys are stored on devices. My users are hybrid identities, synced from on-premises Active Directory to Entra ID. Entra ID issues the Kerberos tickets, so users don’t need line of sight to a domain controller to access the share, although hybrid setups have extra Active Directory requirements, particularly for configuring Windows ACLs (Microsoft Learn: Enable Microsoft Entra Kerberos authentication on Azure Files).

The requirements were strict:

  • Silent. No console windows flashing on screen.
  • Self-healing. If the mapping drops or breaks, it repairs itself without a helpdesk call.
  • Deployed by Intune. No manual configuration on the device.
  • Verified. A drive letter in File Explorer isn’t enough. The script needs to prove the share is actually accessible.

I ended up with one PowerShell script that runs in two modes.

Install mode runs as SYSTEM from Intune. It sets the machine-level configuration the mapping depends on, copies the script to a local folder, registers and configures the user’s scheduled task, creates an optional desktop shortcut, and stamps a version marker in the registry as its final step.

Map mode runs as the logged-on user from the scheduled task. It:

  1. Clears any stale persistent mapping for the drive letter.
  2. Checks the storage account is reachable over SMB.
  3. Purges the user’s cached Kerberos tickets, so the mapping always starts with a fresh ticket.
  4. Maps the drive non-persistently.
  5. Tests the share by listing its contents.
  6. Logs what happened.

The desktop shortcut runs Map mode visibly, with a confirmation message, so a user can reconnect the drive without calling anyone.

The design eventually held up. Getting each part to work reliably is where the six versions came from.

Problem 1: the drive was mapped, but nothing opened

The first failure was the most confusing. Z: appeared in File Explorer, but opening it failed. The mapping existed and the drive letter was there, but Windows couldn’t get into the share.

The diagnostic that cracked it was asking Windows for a Kerberos service ticket for the share directly:

klist get cifs/yourstorageaccount.file.core.windows.net

On the failing machine, there was no ticket. For Entra Kerberos to work, the Windows client has to be able to retrieve the cloud Kerberos ticket, and the client setting that enables this is CloudKerberosTicketRetrievalEnabled. It can be configured through Intune, Group Policy or the registry. I checked it with:

reg query HKLM\SYSTEM\CurrentControlSet\Control\Lsa\Kerberos\Parameters /v CloudKerberosTicketRetrievalEnabled

On several test machines, the value was either missing or hadn’t taken effect yet. That second part matters, because the change isn’t necessarily immediate: a policy refresh or reboot may be needed. A setting that’s present but not yet loaded looks exactly like one that isn’t configured.

The fix

Install mode now writes CloudKerberosTicketRetrievalEnabled = 1 directly, running as SYSTEM. I still configure the setting through the Intune Settings Catalog, which Microsoft recommends over the OMA-URI approach. The registry write is an extra safeguard, not a replacement for proper policy management.

The script also logs the user’s Primary Refresh Token state with dsregcmd /status, looking for AzureAdPrt : YES. The PRT isn’t the only thing that can cause a failure, but it’s an important clue when the client isn’t getting a ticket.

Other prerequisites worth checking early

Two Windows services. Microsoft documents both the WinHTTP Web Proxy Auto-Discovery Service and the IP Helper service as required for Entra Kerberos, and both must be running:

Get-Service WinHttpAutoProxySvc, iphlpsvc

MFA on the storage account’s app. This is one of the most common real-world causes of a missing ticket. Microsoft requires MFA to be disabled on the Entra app that represents the storage account. If you have a Conditional Access policy requiring MFA for all cloud apps, it will silently break Entra Kerberos to Azure Files unless you exclude that app.

App management policies. If your tenant restricts service principal password lifetimes to less than 366 days, or blocks password addition on service principals, you’ll need an exception for the Storage Resource Provider service. It’s niche, but it’s exactly the kind of setting that can stop tickets being issued with no obvious error.

Microsoft’s own diagnostic. When you’re troubleshooting the Azure Files authentication configuration itself, rather than the client script, run Debug-AzStorageAccountAuth. It’s part of the AzFilesHybrid PowerShell module, and from v0.3.0 it detects whether the storage account uses Entra Kerberos and runs the relevant checks, including both services above (AzFilesHybrid releases on GitHub). You’ll need the module installed and an Az PowerShell sign-in:

Debug-AzStorageAccountAuth `
    -StorageAccountName $StorageAccountName `
    -ResourceGroupName $ResourceGroupName `
    -Verbose

It can save a lot of time before you start changing the mapping script.

Problem 2: Test-NetConnection hung the scheduled task

Before mapping the drive, the script checks that TCP 445 to the storage account is reachable. SMB is commonly blocked on public and guest networks, and I’d rather log a clear connectivity failure than have the mapping fail mysteriously.

My first version used Test-NetConnection. Interactively, it worked. Inside the scheduled task, it sometimes hung for a very long time, and the task appeared to have stopped doing anything.

I didn’t need all the diagnostics Test-NetConnection provides. I needed one answer: can this machine open a TCP connection to port 445? So I replaced it with a raw socket connection and a hard timeout:

function Test-Smb445 {
    param(
        [string]$HostName,
        [int]$TimeoutMs = 5000
    )

    $client = New-Object System.Net.Sockets.TcpClient

    try {
        $connect = $client.ConnectAsync($HostName, 445)
        return ($connect.Wait($TimeoutMs) -and $client.Connected)
    }
    catch {
        return $false
    }
    finally {
        $client.Dispose()
    }
}

Now the check succeeds or fails within five seconds, which is what something running unattended needs.

The lesson applies well beyond Azure Files. Anything that runs unattended needs a timeout on network calls. A network test that can wait indefinitely is worse than one that simply fails.

Problem 3: NativeCommandError from net.exe

The script uses $ErrorActionPreference = 'Stop', because I want unexpected errors to stop it rather than let it carry on in a broken state. It also captures net.exe output so it can log what happened. In Windows PowerShell 5.1, that combination has an unpleasant trap.

Before mapping, the script deletes any existing Z: mapping. When there isn’t one, net use Z: /delete writes a perfectly harmless message to stderr: “The network connection could not be found.” Windows PowerShell 5.1 turns redirected stderr from a native executable into a PowerShell error record, and with $ErrorActionPreference = 'Stop', that record terminates the script:

net.exe : The network connection could not be found.
    + CategoryInfo          : NotSpecified: (The network con...d not be found.:String) [], RemoteException
    + FullyQualifiedErrorId : NativeCommandError

So an entirely expected clean-up message killed the whole mapping process.

The fix

I wrapped native commands so PowerShell’s error handling can’t interfere with them, and judged success by the process exit code instead:

function Invoke-Native {
    param(
        [string]$FilePath,
        [string[]]$Arguments
    )

    $previous = $ErrorActionPreference
    $ErrorActionPreference = 'Continue'

    try {
        $output = '' | & $FilePath @Arguments 2>&1 |
            ForEach-Object { "$_" }

        [pscustomobject]@{
            ExitCode = $LASTEXITCODE
            Output   = ($output -join ' ')
        }
    }
    finally {
        $ErrorActionPreference = $previous
    }
}

There’s one detail to handle explicitly. Judging by exit code means the delete step still returns non-zero when there’s nothing to delete, because net.exe reports error 2250. The script treats 2250 on the delete step as expected rather than as a failure. Any other non-zero result is logged as a real error.

The empty string piped into the command is deliberate, and it matters in the next problem.

I also introduced a second bug while fixing this one. I accidentally dropped the use subcommand from the argument list, so the script briefly ran the equivalent of net.exe Z: /delete instead of net.exe use Z: /delete. It was a good reminder to test the whole execution path after changing a helper function, not just the line you think you’ve fixed.

I targeted Windows PowerShell 5.1 because that’s what I needed to support across every device. PowerShell 7 handles native commands differently, but switching to it wasn’t the answer to this problem.

Problem 4: stale tickets and a credential prompt nobody could see

This took the longest to find, and it was the most important problem in the whole exercise.

After the mapping had been working for weeks, it stopped on some devices. The scheduled task ran, never finished, and Z: never came back. When I ran the same mapping by hand in a visible PowerShell window, the cause was obvious: net use was sitting at a username prompt.

The user had a stale Kerberos ticket for the share. Windows presented the cached ticket, the storage account rejected it, and net.exe fell back to asking for credentials. In a visible window, that’s annoying. Inside a hidden scheduled task, it’s a hang. The process is waiting for input from a user who can’t see the prompt.

The proof was simple. Purging the ticket cache and mapping again worked immediately:

klist purge

The script’s diagnostics then showed a fresh ticket for cifs/yourstorageaccount.file.core.windows.net from KERBEROS.MICROSOFTONLINE.COM, and the live access check passed.

The fix had two parts

1. Purge before mapping. Map mode now runs klist purge for the user before every net use, so the mapping starts with a fresh ticket rather than one that may have gone stale.

There’s a trade-off here. klist purge clears all of the user’s cached Kerberos tickets for that logon session, not just the Azure Files one. Windows obtains new tickets when they’re next needed, but a blanket purge can briefly affect other Kerberos-authenticated resources. That’s why I wouldn’t automatically put klist purge into every scheduled task that runs every few minutes. In this case, I accepted the trade-off because the reliability of this mapping mattered more than preserving every cached ticket.

2. Make prompting impossible. That empty string piped into Invoke-Native means that if net.exe does try to ask for credentials, it immediately receives empty input and fails instead of waiting forever. The script then detects the failure and logs it.

This turned into a general rule I now apply everywhere:

In an unattended process, any command that can prompt eventually will.

Never let an unattended task sit waiting for input nobody can provide. Make the failure fast, and make it visible in the logs.

Problem 5: conhost –headless, the wrongly accused suspect

The next requirement was cosmetic but important: the scheduled task must never flash a PowerShell window on the user’s desktop.

powershell.exe -WindowStyle Hidden isn’t enough. PowerShell briefly creates a console window before the hidden style takes effect, so users see a black flash. On a task that triggers at logon, on unlock and on network changes, that’s very noticeable.

The workaround I used was to launch PowerShell through the Windows console host in headless mode:

conhost.exe --headless powershell.exe -NoProfile -ExecutionPolicy Bypass -NonInteractive -WindowStyle Hidden -File "C:\ProgramData\YourOrg\Map-ArchiveDrive.ps1" -Mode Map

This isn’t an Azure Files requirement, and --headless isn’t a formally documented switch. If you use it, test it on the Windows versions you deploy to, and accept that you’re adding another dependency. There’s also a cosmetic side effect: Task Scheduler can keep showing the task as “Running” after PowerShell has finished.

This is where I went wrong. When the stale-ticket problem first appeared, the task seemed to disappear into the headless process and never return. I concluded that conhost --headless was swallowing the PowerShell process, and in one version I removed it.

It wasn’t guilty. The headless window was hiding the real problem, the credential prompt, not causing it. Once the purge and empty-input fixes were in, I put conhost --headless back for the scheduled task, and it has been reliable since.

The lesson is bigger than this script:

A component that hides a failure isn’t necessarily the component causing it.

When you can’t see what a process is doing, reproduce the problem somewhere visible before deciding what’s wrong. That’s exactly why the desktop shortcut runs the mapping in a normal, visible PowerShell window.

Deployment lessons

Getting the script working on one computer was only half the job. Getting it to behave across an Intune-managed fleet took a few more decisions.

Use a Win32 app, not a platform script. I started with an Intune platform script, but platform scripts aren’t a good fit for something whose installed state needs to be detected and repaired. When I deleted the scheduled task on a test machine to reproduce a problem, Intune had no reason to recreate it, because the script itself hadn’t changed. I repackaged it as a Win32 app with a registry detection rule. Install mode writes a version value such as HKLM\SOFTWARE\YourOrg\ArchiveMap\Version as its very last step, so Intune only considers the app installed once every part of the setup has completed. If the detection value is missing, Intune reinstalls it.

Start the task straight after registering it. Install mode calls Start-ScheduledTask immediately after creating the task, so the drive maps as part of deployment instead of waiting for the next logon or unlock.

Map non-persistently. The script maps with /persistent:no and removes any leftover persistent mapping for the drive letter under HKCU\Network. A stale persistent mapping from an earlier attempt can make Windows try to restore the old connection at logon, before the user has a valid ticket, which is exactly the situation I was trying to avoid. The scheduled task becomes the single source of truth for the mapping.

Keep idle connections alive. I set KeepConn under HKLM\SYSTEM\CurrentControlSet\Services\LanmanWorkstation\Parameters to 65535 seconds, which reduces the chance of idle SMB connections being dropped. This isn’t an Azure Files prerequisite. It was a tuning choice for this workload, so evaluate it for your own environment rather than copying it by default.

Watch the authentication noise. Once everything worked, I noticed the task was triggering aggressively at logon, on unlock, on network connection and on a repeating timer, and every run with a ticket purge meant fresh authentication activity. The mapping was reliable, but noisy. I relaxed the trigger frequency and switched the verbose diagnostics to run mainly on failure. There’s a difference between a script being reliable and a script being unnecessarily busy.

Allow for signing and application control. If your environment enforces signed scripts or application control, every script edit can mean re-signing and re-approving the file. That’s a separate problem from Azure Files, but budget time for it when you plan the deployment.

What six versions taught me

Looking back, almost every failure had the same shape: something was happening somewhere I couldn’t see. A setting was present but hadn’t taken effect. A network test could wait forever. A harmless message was treated as fatal. A credential prompt sat inside an invisible scheduled task. And a component that hid the failure was blamed for causing it.

The fixes had a common theme too. Make every important step prove it worked rather than assuming it did. Put a timeout on anything that can wait. Make sure nothing can stop for user input. Judge native commands by their exit code, not by stderr. And when something fails silently, reproduce it somewhere you can see before deciding which component is responsible.

That’s what turned six versions of the script into one I was comfortable deploying.

Checklist: mapping Azure Files with Entra Kerberos

  • Confirm Entra Kerberos is enabled on the storage account, and users or groups have share-level permissions.
  • Exclude the storage account’s Entra app from any Conditional Access policy that requires MFA.
  • Configure CloudKerberosTicketRetrievalEnabled through the Intune Settings Catalog, then restart or refresh policy.
  • Confirm WinHttpAutoProxySvc and iphlpsvc are running.
  • Check the PRT with dsregcmd /status, and test for a ticket with klist get cifs/<storage-account>.file.core.windows.net.
  • Run Debug-AzStorageAccountAuth when you need to validate the storage account’s authentication configuration.
  • Test TCP 445 with a socket and a hard timeout.
  • Wrap net.exe so stderr can’t terminate the script, judge it by $LASTEXITCODE, and treat 2250 on delete as expected.
  • Weigh the trade-off before using klist purge, since it clears all the user’s cached tickets.
  • Make sure unattended commands can never wait for credentials or other input.
  • Map with /persistent:no, and clear stale persistent mappings for the drive letter.
  • Verify access with a real directory listing, not just the presence of a drive letter.
  • Use conhost.exe --headless only if you’ve tested it and genuinely need to eliminate console flashes.
  • Deploy as a Win32 app, stamping the detection value only after every component is installed.
  • Tune task triggers once the mapping is stable, so you’re not generating unnecessary authentication traffic.

Sources

Leave a Reply

Your email address will not be published. Required fields are marked *