Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- Added `-UseDeviceAuthentication` switch to `Connect-FabricAccount` to force device code authentication instead of the interactive broker/browser flow

### Changed

### Fixed

- `Connect-FabricAccount` now automatically falls back to device code authentication when interactive/broker (WAM) sign-in fails, e.g. in terminal hosts such as Warp on Windows where the account picker cannot render

### Deprecated

### Removed
Expand Down
2 changes: 1 addition & 1 deletion RequiredModules.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
Assert = "0.9.6"
InvokeBuild = 'latest'
PSScriptAnalyzer = '1.24.0'
Pester = 'latest'
Pester = '5.9.1'
ModuleBuilder = 'latest'
ChangelogManagement = 'latest'
Sampler = 'latest'
Expand Down
41 changes: 37 additions & 4 deletions src/Public/Connect-FabricAccount.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ function Connect-FabricAccount {
.PARAMETER Reset
A switch parameter. If provided, the function resets the Fabric authentication token.

.PARAMETER UseDeviceAuthentication
A switch parameter. Forces device code authentication instead of the interactive broker/browser flow.
Use this when the interactive account picker cannot be displayed by the host terminal (for example Warp on Windows,
which does not expose a window handle the WAM broker can parent its UI to). When this switch is not specified,
Connect-FabricAccount still falls back to device code authentication automatically if interactive sign-in fails.

.EXAMPLE
Connects to the stated Tenant with existing credentials

Expand Down Expand Up @@ -49,6 +55,13 @@ function Connect-FabricAccount {
Connect-FabricAccount -TenantId $TenantID -ServicePrincipalId $ServicePrincipalId -ServicePrincipalSecret $ServicePrincipalSecretSecure -Reset
```

.EXAMPLE
Connects using device code authentication (useful in terminals such as Warp where the account picker cannot render)

```powershell
Connect-FabricAccount -UseDeviceAuthentication
```

.EXAMPLE
Connects as Service Principal using credential object

Expand All @@ -72,6 +85,7 @@ function Connect-FabricAccount {
- 2024-12-22 - FGE: Added Verbose Output
- 2025-05-26 - Jojobit: Added Service Principal support, with secure string handling and parameter descriptions, as supported by the original FabTools module
- 2025-06-02 - KNO: Added Reset switch to force re-authentication and token refresh
- 2026-08-23 - PBO: Added UseDeviceAuthentication switch and automatic fallback to device code auth when interactive/broker sign-in fails (e.g. Warp terminal)

Author: Frank Geisler, Kamil Nowinski

Expand All @@ -96,7 +110,10 @@ function Connect-FabricAccount {
[PSCredential] $Credential,

[Parameter(Mandatory = $false, HelpMessage = "Refresh current session.")]
[switch] $Reset
[switch] $Reset,

[Parameter(Mandatory = $false, HelpMessage = "Use device code authentication instead of the interactive broker/browser flow.")]
[switch] $UseDeviceAuthentication
)

begin {
Expand Down Expand Up @@ -135,13 +152,29 @@ function Connect-FabricAccount {
}
else {
Write-Message "Connecting to Azure Account using current user..." -Level Verbose
$connectAzAccountParams = @{}
if ($TenantId) {
$null = Connect-AzAccount -Tenant $TenantId
$connectAzAccountParams['Tenant'] = $TenantId
}
else {
# If no TenantId is provided, connect to the default tenant
Write-Message "No TenantId provided, connecting to default tenant..." -Level Verbose
$null = Connect-AzAccount
}

if ($UseDeviceAuthentication) {
Write-Message "Using device code authentication as requested..." -Level Verbose
$null = Connect-AzAccount @connectAzAccountParams -UseDeviceAuthentication
}
else {
try {
$null = Connect-AzAccount @connectAzAccountParams -ErrorAction Stop
}
catch {
# Interactive/broker (WAM) sign-in can silently fail to render its account picker in some
# terminal hosts (e.g. Warp on Windows does not expose a window handle for WAM to parent
# its UI to). Fall back to device code authentication instead of failing outright.
Write-Message "Interactive sign-in failed, possibly because this terminal does not support the account picker (e.g. Warp): $($_.Exception.Message). Falling back to device code authentication..." -Level Warning
$null = Connect-AzAccount @connectAzAccountParams -UseDeviceAuthentication
}
}
}
$azContext = Get-AzContext
Expand Down
62 changes: 62 additions & 0 deletions tests/Unit/Connect-FabricAccount.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ Describe "Connect-FabricAccount" -Tag "UnitTests" {
@{ ExpectedParameterName = 'ServicePrincipalSecret'; ExpectedParameterType = 'securestring'; Mandatory = 'False' }
@{ ExpectedParameterName = 'Credential'; ExpectedParameterType = 'pscredential'; Mandatory = 'False' }
@{ ExpectedParameterName = 'Reset'; ExpectedParameterType = 'switch'; Mandatory = 'False' }
@{ ExpectedParameterName = 'UseDeviceAuthentication'; ExpectedParameterType = 'switch'; Mandatory = 'False' }
) {
$Command | Should -HaveParameter -ParameterName $ExpectedParameterName -Type $ExpectedParameterType -Mandatory:([bool]::Parse($Mandatory))
}
Expand All @@ -31,4 +32,65 @@ Describe "Connect-FabricAccount" -Tag "UnitTests" {
$Command.Parameters.ContainsKey('Confirm') | Should -BeTrue
}
}

Context "Device code authentication fallback" {

BeforeEach {
Mock -CommandName Get-AzContext -MockWith { $null }
Mock -CommandName Write-Message -MockWith { }
Mock -CommandName Get-PSFConfigValue -MockWith { 'https://api.fabric.microsoft.com' }
Mock -CommandName Set-PSFConfig -MockWith { }
Mock -CommandName Get-AzAccessToken -MockWith {
[pscustomobject]@{
Token = (ConvertTo-SecureString -String 'fake-token' -AsPlainText -Force)
ExpiresOn = (Get-Date).AddHours(1)
TenantId = [guid]::NewGuid()
}
}
}

It 'Should use interactive Connect-AzAccount when broker sign-in succeeds' {
$script:connectCallLog = [System.Collections.Generic.List[bool]]::new()
Mock -CommandName Connect-AzAccount -MockWith {
$script:connectCallLog.Add([bool]$UseDeviceAuthentication)
[pscustomobject]@{ Context = [pscustomobject]@{ Account = 'user@contoso.com'; Tenant = [pscustomobject]@{ Id = [guid]::NewGuid() } } }
}

Connect-FabricAccount -Confirm:$false

$script:connectCallLog.Count | Should -Be 1
$script:connectCallLog[0] | Should -BeFalse
}

It 'Should fall back to device code authentication when interactive Connect-AzAccount fails' {
$script:connectCallLog = [System.Collections.Generic.List[bool]]::new()
Mock -CommandName Connect-AzAccount -MockWith {
$script:connectCallLog.Add([bool]$UseDeviceAuthentication)
if (-not $UseDeviceAuthentication) {
throw 'No account picker UI could be shown.'
}
[pscustomobject]@{ Context = [pscustomobject]@{ Account = 'user@contoso.com'; Tenant = [pscustomobject]@{ Id = [guid]::NewGuid() } } }
}

{ Connect-FabricAccount -Confirm:$false } | Should -Not -Throw

$script:connectCallLog.Count | Should -Be 2
$script:connectCallLog[0] | Should -BeFalse # first attempt: interactive, fails
$script:connectCallLog[1] | Should -BeTrue # second attempt: device code fallback, succeeds
Should -Invoke -CommandName Write-Message -ParameterFilter { $Level -eq 'Warning' -and $Message -like '*Falling back to device code authentication*' }
}

It 'Should go straight to device code authentication when -UseDeviceAuthentication is specified' {
$script:connectCallLog = [System.Collections.Generic.List[bool]]::new()
Mock -CommandName Connect-AzAccount -MockWith {
$script:connectCallLog.Add([bool]$UseDeviceAuthentication)
[pscustomobject]@{ Context = [pscustomobject]@{ Account = 'user@contoso.com'; Tenant = [pscustomobject]@{ Id = [guid]::NewGuid() } } }
}

Connect-FabricAccount -UseDeviceAuthentication -Confirm:$false

$script:connectCallLog.Count | Should -Be 1
$script:connectCallLog[0] | Should -BeTrue
}
}
}
Loading