src/_Invoke-CciCredentialProvider.ps1
|
function _Invoke-CciCredentialProvider { <# .SYNOPSIS Acquires a feed token by running the Azure Artifacts Credential Provider in stand-alone mode (interactive device-code flow). .DESCRIPTION On a fresh machine with no Azure CLI session and no cached provider token, PSResourceGet invokes the credential provider in PLUGIN mode, which cannot surface an interactive prompt — it just fails (exit code 2). Running the provider directly in stand-alone mode does present the device-code flow and populates its session token cache. Returns the acquired token (string) on success, $null on failure. Device-code instructions are written by the provider to stderr, which is left attached to the console so the user can see and act on them; the token itself comes back on stdout as JSON and is never printed. .PARAMETER FeedUrl Package source URI to authenticate against. #> [CmdletBinding()] param( [Parameter(Mandatory)] [string]$FeedUrl ) $exe = Join-Path $env:USERPROFILE '.nuget\plugins\netcore\CredentialProvider.Microsoft\CredentialProvider.Microsoft.exe' if (-not (Test-Path $exe)) { $found = Get-ChildItem (Join-Path $env:USERPROFILE '.nuget\plugins') -Recurse -Filter 'CredentialProvider.Microsoft.exe' -ErrorAction SilentlyContinue | Select-Object -First 1 if (-not $found) { Write-Verbose "cciget: credential provider executable not found; cannot run interactive sign-in." return $null } $exe = $found.FullName } Write-Host "cciget: signing in to the feed (device code follows; complete it in a browser)..." # The provider's device-flow timeout defaults to 90 seconds, which routinely # expires while a human opens a browser and signs in - surfacing only as # "[CredentialProvider] A task was canceled" and exit code 2. Allow 10 min. if (-not $env:ARTIFACTS_CREDENTIALPROVIDER_DEVICEFLOWTIMEOUTSECONDS) { $env:ARTIFACTS_CREDENTIALPROVIDER_DEVICEFLOWTIMEOUTSECONDS = '600' } if (-not $env:NUGET_CREDENTIALPROVIDER_VSTS_DEVICEFLOWTIMEOUTSECONDS) { $env:NUGET_CREDENTIALPROVIDER_VSTS_DEVICEFLOWTIMEOUTSECONDS = '600' } # Native tools write progress/instructions to stderr. Under # $ErrorActionPreference = 'Stop' the FIRST such line becomes a terminating # error before the exit code can be inspected, so relax it locally and gate # strictly on $LASTEXITCODE. $previousEap = $ErrorActionPreference $ErrorActionPreference = 'Continue' try { # -C false : force device-code flow instead of a UI dialog (no broker on # a freshly built machine, and OOBE shells are often headless) # -I : do not return possibly-stale cached credentials # -F Json : token on stdout; human instructions (incl. the device code) # go to stderr, which stays attached to the console $stdout = & $exe -U $FeedUrl -C false -I -V Information -F Json $exit = $LASTEXITCODE } finally { $ErrorActionPreference = $previousEap } if ($exit -ne 0) { Write-Verbose "cciget: credential provider exited with code $exit." if (($stdout | Out-String) -match 'task was canceled') { Write-Warning "cciget: the device-code sign-in timed out before it was completed. Run Connect-CciGet again and finish the browser sign-in promptly." } return $null } $joined = ($stdout | Out-String).Trim() if (-not $joined) { return $null } try { $parsed = $joined | ConvertFrom-Json } catch { Write-Verbose "cciget: could not parse credential provider output as JSON." return $null } $passwordProp = $parsed.PSObject.Properties['Password'] if (-not $passwordProp -or -not $passwordProp.Value) { return $null } return $passwordProp.Value } |