src/auth/CompiledDeviceCodeAuthenticator.ps1
# Copyright 2024, Adam Edwards # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. $authLibraryPath = [System.Reflection.Assembly]::GetAssembly(([Microsoft.Identity.Client.PublicClientApplication])).location # Use C# here due to threading complications when using AcquireTokenWithDeviceCodeAsync. # try { add-type -referencedassemblies $authLibraryPath, System.Console, System.Threading.Tasks, System.Threading, System.Runtime.Extensions -ignorewarnings @' using System; using System.IO; using System.Threading; using System.Threading.Tasks; using Microsoft.Identity.Client; public class CompiledDeviceCodeAuthenticator { public static ManualResetEvent messageReadyEvent; public static MemoryStream messageStream; public static Task ShowMessage(DeviceCodeResult deviceCodeResult) { // Note that here instead of performing a Console.WriteLine for the message, // we actually write it to a memory stream. This handles a particular situation with // PowerShell remote sessions where Console.WriteLine is actually not // writing to the PowerShell host, it's simply "lost" to a .NET console that is not // associated to the PowerShell host that displays text to the user. To work around this, // we send the message to a stream supplied by the caller of GetTokenWithCode -- the caller // is expected to read the message from that stream once we signal the event also // supplied by this caller in the method below. That caller which is presumably running with // the ability to invoke 'write-host' to the PowerShell host can then display the message // to the user who can then complete the actions necessary for the original call to acquire // the token to complete. StreamWriter writer = new StreamWriter(messageStream, System.Text.Encoding.Unicode); writer.WriteLine(deviceCodeResult.Message); // Write the user prompt message to the stream writer.Flush(); messageReadyEvent.Set(); // Notify the initiator that the user prompt with device code information is ready to read return Task.FromResult(0); } public static Task<AuthenticationResult> GetTokenWithCode(PublicClientApplication authContext, string[] scopes, MemoryStream messageStream, ManualResetEvent messageReadyEvent) { CompiledDeviceCodeAuthenticator.messageStream = messageStream; CompiledDeviceCodeAuthenticator.messageReadyEvent = messageReadyEvent; var asyncResult = authContext.AcquireTokenWithDeviceCode( scopes, ShowMessage).ExecuteAsync(); return asyncResult; } } '@ 3> $null } catch { write-warning "Unable to compile code for device code authentication flow -- device code signin will be disabled" write-warning $_.Exception } |