top of page
Search

The Case of the Sign-In Window That Refused to Show Up

  • Writer: Shannon
    Shannon
  • 2 minutes ago
  • 10 min read

Every platform migration eventually presents its toll booth.


I have been doing more of my day-to-day Azure work from a Mac lately, which meant getting PowerShell, the Az modules, Git, and VS Code all cooperating on the same machine. Most of it was surprisingly uneventful. PowerShell runs perfectly well on macOS, the Azure modules install normally, and VS Code gives me roughly the same scripting experience I am used to everywhere else.


Then I ran the single most routine command in Azure PowerShell via VS Code:

Connect-AzAccount

Instead of the familiar Microsoft sign-in page, PowerShell handed me this:

WARNING: Unable to acquire token for tenant 'organizations' with error
'InteractiveBrowserCredential authentication failed:
Interactive requests with mac broker enabled must be executed on the
main thread on macOS.'

WARNING: Please run 'Connect-AzAccount -DeviceCode' if browser is not
supported in this session.

Connect-AzAccount: InteractiveBrowserCredential authentication failed:
Interactive requests with mac broker enabled must be executed on the
main thread on macOS.

That is a lot of error message for what turned out to be a fairly small problem. My account was fine. The tenant was fine. PowerShell was fine. The interactive login flow simply could not present its sign-in window from the PowerShell session VS Code was hosting.


If you landed here from a search engine with that exact error on your screen, the fix is one command and then it comes right up. But the error itself is a nice little tour of how VS Code, PowerShell, the authentication stack, and macOS actually fit together, and knowing that machinery is what turns "I copied a fix from a blog" into "I understand my workstation." Stick around for that part.


The Fix That Got Me Moving Again

The warning in the output tells you what to do, although the fully documented parameter name is slightly different. Instead of letting Connect-AzAccount pick an interactive browser flow, force device code authentication:

Connect-AzAccount -UseDeviceAuthentication

Depending on the version of Az.Accounts installed, you may also see or use the shorter alias from the warning:

Connect-AzAccount -DeviceCode

PowerShell hands you a URL and a temporary code. Open the URL in any browser, enter the code, complete authentication, and return to your session. PowerShell detects that the login finished and carries on like nothing happened.


Before you feel like you settled for the backup plan: you did not. Microsoft's interactive sign-in documentation documents device authentication as the supported alternative whenever the browser experience cannot open correctly. It's a standard OAuth device authorization flow, purpose-built for terminals, SSH sessions, headless boxes, and any other place where popping a login window is...well...awkward. Not a lesser method (by any means). Not a sketchy workaround. Just the flow that does not care where your process is running.


Once connected, I verify both the account and the subscription context before running anything interesting:

Get-AzContext

Get-AzSubscription |
    Select-Object Name, Id, TenantId, State

And if I need a specific subscription, I set it explicitly rather than trusting whichever one happened to become active during login:

Set-AzContext -Subscription '<subscription-name-or-id>'

That was enough to get back to work. But the error was too interesting to leave alone, because it briefly exposed machinery that usually stays hidden.


What Does "Must Be Executed on the Main Thread" Actually Mean?

The authentication stack tried to use an interactive, broker-enabled sign-in flow. On macOS, a native authentication prompt must be launched from the application's main user-interface thread. If anything tries to launch that prompt from a background thread or a noninteractive context, macOS says no, and Microsoft's authentication library returns the exact exception I received.


Here is why VS Code matters. PowerShell inside VS Code is not a traditional standalone desktop application. VS Code starts and hosts a PowerShell process, and the PowerShell extension layers on language services, a debugger, session management, and the extension terminal. Wonderful for writing code. Less wonderful for an authentication library that expects to see a normal application context with a main UI thread it can borrow.


There is a second wrinkle for company-managed Macs. Microsoft's macOS authentication broker is associated with the Company Portal application and the macOS single sign-on extension. If you are on a managed corporate Mac (like me), that management and SSO tooling is likely the reason the authentication stack attempted a brokered flow in the first place. (The exact path depends on your module and library versions, but the pattern of "works on my personal Mac, fails on my work Mac" often traces back to exactly this.)


The practical takeaway: your password is not wrong, and the tenant named organizations is not broken. The login flow failed before it ever got to the part where credentials matter.


Is VS Code Actually the Problem?

Possibly. But test it before making any dramatic changes to your setup.


Open the regular macOS Terminal application and start PowerShell directly:

pwsh

Then try the original command again:

Connect-AzAccount

If browser authentication works in Terminal but fails inside VS Code, the difference is the host, or the way that particular PowerShell process was launched. VS Code's own terminal troubleshooting guidance recommends exactly this comparison, because it quickly tells you whether the problem belongs to the shell or to the thing hosting the shell.


If the command fails in both places, the problem is bigger than VS Code, and the next suspects are the Az module version, the PowerShell version, and whether multiple copies of either one are installed. Device code authentication will still get you moving either way, but now you are troubleshooting the machine rather than the editor.


Here is a quick way to see what is actually running:

[pscustomobject]@{
    PowerShellVersion = $PSVersionTable.PSVersion
    PowerShellEdition = $PSVersionTable.PSEdition
    OperatingSystem   = $PSVersionTable.OS
    ProcessPath       = [Environment]::ProcessPath
    PSHome            = $PSHOME
}

Run that once in Terminal and once in VS Code. If the two environments are running different PowerShell installations, ProcessPath will tell on them immediately.


Making Sure VS Code Is Using the Right PowerShell

A detail that trips people up: installing the PowerShell extension does not install PowerShell. The extension provides language support, IntelliSense, linting, debugging, formatting, and the integrated experience, but it still needs a supported PowerShell 7 installation on the Mac. Microsoft currently tests the VS Code PowerShell extension on macOS with PowerShell 7 and lets you choose among the supported versions installed on the machine.


First, confirm macOS can find pwsh at all:

which pwsh

Start it and check the version:

pwsh

$PSVersionTable

Inside VS Code, open the Command Palette with Command+Shift+P and run:

text

PowerShell: Show Session Menu

That menu lists every PowerShell installation the extension has detected and lets you pick which one hosts the current session. Check this if you have upgraded PowerShell, installed a preview build, migrated from an Intel Mac to Apple Silicon, or installed PowerShell through more than one package manager. (Ask me how I know that last combination is possible...actually on second thought, don't.)


After selecting a session, restart it from the Command Palette:

PowerShell: Restart Current Session

Then verify the executable one more time:

[Environment]::ProcessPat

$PSVersionTable.PSVersion

As a bonus, the extension bundles PSScriptAnalyzer and lints your PowerShell files as you edit. That alone is worth using the extension rather than treating a .ps1 file like generic text and running everything by hand in a terminal.


Installing PowerShell Correctly on Apple Silicon

Before installing any command-line tooling, know which processor you are working with. From the normal macOS shell:

uname -m

Apple Silicon returns:

arm64

An Intel Mac returns:

x86_64

Microsoft publishes separate ARM64 and x64 PowerShell packages for macOS, and the current packages are notarized and signed, which makes installation cleaner than some older releases that used to trip Gatekeeper warnings. The official PowerShell installation documentation for macOS walks through the Microsoft package, which creates a symbolic link at:

/usr/local/bin/pwsh

and installs the stable files under:

/usr/local/microsoft/powershell/7/

You can also go the Homebrew route:

brew install powershell

One distinction worth knowing here: Microsoft documents the Homebrew method, but the current Homebrew formula is maintained by the Homebrew community rather than Microsoft. On a personal machine that may not matter at all. On a tightly managed workstation, I would at least know which installation source I am running before troubleshooting version or support issues, because "who owns this binary" is the kind of question that always comes up at the worst possible time.


To update a Homebrew installation:

brew update
brew upgrade powershell

And if pwsh is installed but not linked correctly:

brew link powershell

Whichever method you pick, pick one. A copy from the Microsoft package, another from Homebrew, and maybe a third installed as a .NET global tool is a reliable recipe for Terminal and VS Code quietly selecting different executables, which is exactly the class of problem the validation script later in this post exists to catch.


Installing and Checking the Az Modules

Once PowerShell itself is behaving, install the Az rollup module from a PowerShell session:

Install-Module -Name Az -Repository PSGallery -Force

Microsoft supports the Az module on macOS with PowerShell 7 or later, and the installation process is the same one you already know from other platforms. Connect-AzAccount remains the normal entry point afterward.


To see which versions are installed:

Get-Module -Name Az.Accounts -ListAvailable |
    Sort-Object Version -Descending |
    Select-Object Name, Version, ModuleBase

I include ModuleBase deliberately, because it tells me where PowerShell found the module. That matters when an old copy is sitting in one module path while the shiny new version got installed somewhere else entirely.


You can inspect all the module search paths with:

$env:PSModulePath -split [IO.Path]::PathSeparator

To update the full Az module:

Update-Module -Name Az -Force

Updating does not automatically remove older versions. If Get-Module -ListAvailable starts looking like an archaeological dig, review the installed versions and remove the obsolete layers deliberately rather than assuming the newest install replaced everything.


One more habit worth building: restart the VS Code PowerShell session after installing or updating modules. A module already imported into the current process will not magically swap itself out just because a newer version appeared on disk.


A Small Mac and VS Code Validation Script

I put this check together because it answers most of the questions I have when PowerShell behaves differently between Terminal and VS Code:

$azAccounts = Get-Module -Name Az.Accounts -ListAvailable |
    Sort-Object Version -Descending |
    Select-Object -First 1

$validation = [ordered]@{
    PowerShellVersion = $PSVersionTable.PSVersion.ToString()
    PowerShellEdition = $PSVersionTable.PSEdition
    OperatingSystem   = $PSVersionTable.OS
    Architecture      = [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture
    ProcessPath       = [Environment]::ProcessPath
    PSHome            = $PSHOME
    CurrentDirectory  = (Get-Location).Path
    AzAccountsVersion = $azAccounts.Version
    AzAccountsPath    = $azAccounts.ModuleBase
}

[pscustomobject]$validation | Format-List

Reading the output is quick. If AzAccountsVersion and AzAccountsPath are blank, the session cannot find the module. If ProcessPath differs between VS Code and Terminal, the two environments are not running the same PowerShell installation. If Architecture reports x64 on an Apple Silicon Mac, Rosetta or an Intel build is in the mix.

None of those conditions automatically mean something is broken. But every one of them gives you something more actionable than "VS Code is being weird."


The Execution Policy Is Probably Not Blocking Your Script

Windows PowerShell muscle memory will send you straight to this:

Get-ExecutionPolicy

On macOS, that returns Unrestricted, and it will always return Unrestricted, because PowerShell does not enforce execution policies outside Windows. Which means this old friend does exactly nothing here:

Set-ExecutionPolicy RemoteSigned

Microsoft's documentation on PowerShell differences across platforms is explicit: execution policies are ignored on Linux and macOS, and Set-ExecutionPolicy is a no-op there.


That does not mean macOS has no security controls around downloaded code. Gatekeeper, quarantine attributes, application permissions, endpoint security tools, and corporate device-management policies all still apply. It only means the Windows-style execution policy is not the control doing the blocking.


I call this out because a tremendous amount of generic PowerShell troubleshooting content still opens with "change your execution policy." On a Mac, that step is pure ritual. Skip it and spend the time on something that can actually be the problem.


The Other Cross-Platform Gotchas Worth Checking

PowerShell is cross-platform. Not every PowerShell script or module is. PowerShell on macOS runs on modern .NET rather than the Windows-only .NET Framework, and many cmdlets that manage Windows services, the registry, local accounts, or WSMan configuration simply do not exist there.


Before running a script written on Windows, I scan for assumptions like these:

C:\Scripts\Something
Get-Service
Get-AuthenticodeSignature
New-ItemProperty -Path 'HKLM:\...'
Import-Module ActiveDirectory

A script that mostly calls Azure REST APIs or leans on the Az modules has a good chance of working across platforms. A script that mixes Azure work with local Windows administration does not.


Casing can bite too. PowerShell command names stay case-insensitive everywhere, but the underlying filesystem can expose incorrect casing in filenames and module paths. A script that references ./Configuration/settings.json may fail when the real directory is named configuration, and that failure will not announce itself clearly.


Paths deserve a look as well. My prompt happened to show this working directory:

/Users/shannon.kuehn/Library/CloudStorage/OneDrive - Shakeen/Documents/source/github

Those spaces did not cause the authentication error. PowerShell already knew its working directory, and Connect-AzAccount never needed to parse the path. But characters like that absolutely matter the moment a path becomes a literal argument, so quote anything containing spaces or shell-significant characters:

Set-Location '/Users/shannon.kuehn/Library/CloudStorage/OneDrive - Shakeen/Documents/source/github'

For scripts, I prefer building paths over gluing slash-delimited strings together by hand:

$repoPath = Join-Path $HOME 'Library/CloudStorage/OneDrive - Shakeen/Documents/source/github'
Set-Location $repoPath

Join-Path reads better and gives the script a real shot at staying portable.


My Working Login Pattern on the Mac

My first draft of an interactive login block looked like this:

Connect-AzAccount -UseDeviceAuthentication

$subscription = Get-AzSubscription |
    Sort-Object Name |
    Out-GridView -Title 'Select an Azure subscription' -PassThru

Set-AzContext -SubscriptionId $subscription.Id

One problem: Out-GridView is not available on macOS. After this post, I really need to write a follow-on blog post about Windows assumptions sneaking into cross-platform scripts and how to avoid that for the future. Whoops! (Consider that a free lesson in how sneaky these assumptions are.)


The Mac-friendly version:

Connect-AzAccount -UseDeviceAuthentication

Get-AzSubscription |
    Sort-Object Name |
    Format-Table Name, Id, TenantId

$subscriptionId = Read-Host 'Enter the subscription ID'
Set-AzContext -SubscriptionId $subscriptionId

For a script where I already know the target, I skip the menu entirely:

$tenantId       = '<tenant-id>'
$subscriptionId = '<subscription-id>'

Connect-AzAccount ` `
    -Tenant $tenantId `
    -Subscription $subscriptionId ` `
    -UseDeviceAuthentication

That removes all ambiguity when the account has access to multiple tenants and subscriptions, and it makes the intended operating context visible in the script instead of relying on whatever context happened to be active yesterday.


One boundary worth stating plainly: device code is for humans. For unattended automation, do not use an interactive user login at all. A managed identity, workload identity, service principal with a certificate, or federated credential is the right fit depending on where the automation runs. Device code gets a person into a PowerShell session. It is not an automation strategy.


Where I Landed

The immediate fix was one command:

Connect-AzAccount -UseDeviceAuthentication

The more durable lesson was learning to check which PowerShell process VS Code was actually hosting, where its modules were coming from, and which Windows habits do not survive the trip to macOS. None of that requires treating a Mac like some exotic Azure workstation. It just requires remembering that VS Code, PowerShell, the Az module, the authentication library, Company Portal, and macOS are six separate pieces of a stack that only feels like one thing when everything works.


When they line up, Azure PowerShell on a Mac is genuinely pleasant. When they do not, the error messages will make a failed login window sound like the end of the world. It almost never is. Usually it is one command, one session menu, or one archaeological dig through your module paths away from fixed.

© 2020 Shannon B. Eldridge-Kuehn

  • LinkedIn
  • Twitter
bottom of page