diff --git a/src/Authentication/Authentication.Core/Common/GraphSession.cs b/src/Authentication/Authentication.Core/Common/GraphSession.cs index 9de7e41c12d..e88356c011d 100644 --- a/src/Authentication/Authentication.Core/Common/GraphSession.cs +++ b/src/Authentication/Authentication.Core/Common/GraphSession.cs @@ -6,6 +6,7 @@ namespace Microsoft.Graph.PowerShell.Authentication { using Microsoft.Graph.PowerShell.Authentication.Core; using Microsoft.Graph.PowerShell.Authentication.Interfaces; + using System; using System.Security; using System.Threading; @@ -200,5 +201,22 @@ public static void Reset() throw new InvalidOperationException(ErrorConstants.Codes.SessionLockWriteDisposed, disposedException); } } + + private IPSGraphOutputWriter _outputWriter; + /// + /// Provides Access to output methods provided by the Cmdlet + /// + public IPSGraphOutputWriter OutputWriter + { + get + { + if (_outputWriter == null) + { + throw new InvalidOperationException(ErrorConstants.Codes.OutputNotInitialized); + } + return _outputWriter; + } + set => _outputWriter = value; + } } } diff --git a/src/Authentication/Authentication.Core/ErrorConstants.cs b/src/Authentication/Authentication.Core/ErrorConstants.cs index 3ee456579e6..6ba7e41f631 100644 --- a/src/Authentication/Authentication.Core/ErrorConstants.cs +++ b/src/Authentication/Authentication.Core/ErrorConstants.cs @@ -15,6 +15,7 @@ internal static class Codes internal const string SessionLockWriteDisposed = "sessionLockWriteDisposed"; internal const string SessionLockWriteRecursion = "sessionLockWriteRecursion"; internal const string InvalidJWT = "invalidJWT"; + internal const string OutputNotInitialized = "outputNotInitialized"; } public static class Message diff --git a/src/Authentication/Authentication.Core/Interfaces/IAuthContext.cs b/src/Authentication/Authentication.Core/Interfaces/IAuthContext.cs index 11994fd0b35..edcc613e11e 100644 --- a/src/Authentication/Authentication.Core/Interfaces/IAuthContext.cs +++ b/src/Authentication/Authentication.Core/Interfaces/IAuthContext.cs @@ -18,6 +18,7 @@ public enum ContextScope Process, CurrentUser } + public enum AuthProviderType { InteractiveAuthenticationProvider, @@ -26,6 +27,7 @@ public enum AuthProviderType ClientCredentialProvider, UserProvidedToken } + public interface IAuthContext { string ClientId { get; set; } @@ -40,4 +42,4 @@ public interface IAuthContext ContextScope ContextScope { get; set; } X509Certificate2 Certificate { get; set; } } -} +} \ No newline at end of file diff --git a/src/Authentication/Authentication.Core/Interfaces/IPSGraphOutputWriter.cs b/src/Authentication/Authentication.Core/Interfaces/IPSGraphOutputWriter.cs new file mode 100644 index 00000000000..bca5a01bbdb --- /dev/null +++ b/src/Authentication/Authentication.Core/Interfaces/IPSGraphOutputWriter.cs @@ -0,0 +1,13 @@ +using System; + +namespace Microsoft.Graph.PowerShell.Authentication +{ + public interface IPSGraphOutputWriter + { + Action WriteObject { get; set; } + Action WriteDebug { get; set; } + Action WriteError { get; set; } + Action WriteInformation { get; set; } + Action WriteVerbose { get; set; } + } +} \ No newline at end of file diff --git a/src/Authentication/Authentication.Core/Utilities/AuthenticationHelpers.cs b/src/Authentication/Authentication.Core/Utilities/AuthenticationHelpers.cs index 644d37302c5..d75ad1e2d78 100644 --- a/src/Authentication/Authentication.Core/Utilities/AuthenticationHelpers.cs +++ b/src/Authentication/Authentication.Core/Utilities/AuthenticationHelpers.cs @@ -82,7 +82,11 @@ public static IAuthenticationProvider GetAuthProvider(IAuthContext authContext) case AuthProviderType.DeviceCodeProvider: case AuthProviderType.DeviceCodeProviderFallBack: authProvider = new DeviceCodeProvider(publicClientApp, authContext.Scopes, - async result => { await Console.Out.WriteLineAsync(result.Message); }); + result => + { + GraphSession.Instance.OutputWriter.WriteObject(result.Message); + return Task.CompletedTask; + }); break; case AuthProviderType.InteractiveAuthenticationProvider: authProvider = new InteractiveAuthenticationProvider(publicClientApp, authContext.Scopes); diff --git a/src/Authentication/Authentication.Test/Helpers/GraphSessionTests.cs b/src/Authentication/Authentication.Test/Helpers/GraphSessionTests.cs index cbc0ae6456e..288b45dd826 100644 --- a/src/Authentication/Authentication.Test/Helpers/GraphSessionTests.cs +++ b/src/Authentication/Authentication.Test/Helpers/GraphSessionTests.cs @@ -1,10 +1,23 @@ -namespace Microsoft.Graph.Authentication.Test.Helpers +using Microsoft.Graph.PowerShell.Authentication.Common; +using Microsoft.Graph.PowerShell.Authentication.Models; + +using Xunit.Abstractions; + +namespace Microsoft.Graph.Authentication.Test.Helpers { using Microsoft.Graph.PowerShell.Authentication; + using System; + using Xunit; public class GraphSessionTests { + private readonly ITestOutputHelper _helper; + + public GraphSessionTests(ITestOutputHelper helper) + { + _helper = helper; + } [Fact] public void GraphSessionShouldBeInitilizedAfterInitializerIsCalled() { @@ -16,7 +29,7 @@ public void GraphSessionShouldBeInitilizedAfterInitializerIsCalled() // reset static instance. GraphSession.Reset(); } - + [Fact] public void ShouldOverwriteExistingGraphSession() { @@ -30,8 +43,8 @@ public void ShouldOverwriteExistingGraphSession() // reset static instance. GraphSession.Reset(); - } - + } + [Fact] public void ShouldNotOverwriteExistingGraphSession() { @@ -54,6 +67,38 @@ public void ShouldThrowExceptionWhenSessionIsNotInitialized() Assert.Equal(PowerShell.Authentication.Core.ErrorConstants.Codes.SessionNotInitialized, exception.Message); + // reset static instance. + GraphSession.Reset(); + } + [Fact] + public void ShouldThrowExceptionWhenOutputIsNotInitialized() + { + GraphSession.Initialize(() => new GraphSession()); + InvalidOperationException exception = Assert.Throws(() => GraphSession.Instance.OutputWriter.WriteObject("Output")); + + Assert.NotNull(GraphSession.Instance); + Assert.Null(GraphSession.Instance.AuthContext); + + // reset static instance. + GraphSession.Reset(); + } + [Fact] + public void ShouldInitializeOutputWriter() + { + GraphSessionInitializer.InitializeSession(); + GraphSessionInitializer.InitializeOutput(new PsGraphOutputWriter + { + WriteError = (exception1, s, arg3, arg4) => _helper.WriteLine(exception1.Message), + WriteObject = _helper.WriteLine, + WriteDebug = _helper.WriteLine, + WriteInformation = (o, s) => _helper.WriteLine(s), + WriteVerbose = _helper.WriteLine + }); + GraphSession.Instance.OutputWriter.WriteObject("Output"); + + Assert.NotNull(GraphSession.Instance.OutputWriter); + Assert.NotNull(GraphSession.Instance.OutputWriter.WriteObject); + // reset static instance. GraphSession.Reset(); } diff --git a/src/Authentication/Authentication.Test/TokenCache/ProcessTokenCacheStorageTests.cs b/src/Authentication/Authentication.Test/TokenCache/ProcessTokenCacheStorageTests.cs index a9d0582c157..98ec575a5e9 100644 --- a/src/Authentication/Authentication.Test/TokenCache/ProcessTokenCacheStorageTests.cs +++ b/src/Authentication/Authentication.Test/TokenCache/ProcessTokenCacheStorageTests.cs @@ -1,21 +1,36 @@ namespace Microsoft.Graph.Authentication.Test.TokenCache { using Microsoft.Graph.PowerShell.Authentication; + using Microsoft.Graph.PowerShell.Authentication.Models; using Microsoft.Graph.PowerShell.Authentication.Common; using Microsoft.Graph.PowerShell.Authentication.TokenCache; + using System; using System.Text; using System.Threading; + using Xunit; + using Xunit.Abstractions; - public class ProcessTokenCacheStorageTests: IDisposable + public class ProcessTokenCacheStorageTests : IDisposable { // Defaults to process context scope. private IAuthContext _testAppContext1; - public ProcessTokenCacheStorageTests() + public ProcessTokenCacheStorageTests(ITestOutputHelper outputHelper) { _testAppContext1 = new AuthContext { ClientId = "test_app_id_1" }; GraphSessionInitializer.InitializeSession(); + GraphSessionInitializer.InitializeOutput(new PsGraphOutputWriter + { + WriteError = (exception, s, arg3, arg4) => + { + outputHelper.WriteLine(exception.Message); + }, + WriteDebug = outputHelper.WriteLine, + WriteInformation = (o, strings) => outputHelper.WriteLine(o.ToString()), + WriteObject = outputHelper.WriteLine, + WriteVerbose = outputHelper.WriteLine + }); } [Fact] @@ -93,7 +108,8 @@ public void ProccessTokenCacheShouldBeThreadSafe() // Act for (int i = 0; i < threads.Length; i++) { - threads[i] = new Thread(() => { + threads[i] = new Thread(() => + { byte[] contentBuffer = Encoding.UTF8.GetBytes(i.ToString()); TokenCacheStorage.SetToken(_testAppContext1, contentBuffer); Thread.Sleep(2000); diff --git a/src/Authentication/Authentication/Cmdlets/ConnectMgGraph.cs b/src/Authentication/Authentication/Cmdlets/ConnectMgGraph.cs index da7752f5726..ac527060cff 100644 --- a/src/Authentication/Authentication/Cmdlets/ConnectMgGraph.cs +++ b/src/Authentication/Authentication/Cmdlets/ConnectMgGraph.cs @@ -137,6 +137,8 @@ protected override void ProcessRecord() { using (var asyncCommandRuntime = new CustomAsyncCommandRuntime(this, _cancellationTokenSource.Token)) { + // Init output to this Cmdlet + GraphSessionInitializer.InitializeOutput(asyncCommandRuntime); asyncCommandRuntime.Wait(ProcessRecordAsync(), _cancellationTokenSource.Token); } } @@ -174,7 +176,6 @@ private async Task ProcessRecordAsync() IAuthContext authContext = new AuthContext { TenantId = TenantId }; // Set selected environment to the session object. GraphSession.Instance.Environment = environment; - switch (ParameterSetName) { case Constants.UserParameterSet: @@ -225,7 +226,7 @@ private async Task ProcessRecordAsync() _cancellationTokenSource.Token, () => { WriteWarning(Resources.DeviceCodeFallback); }); } - catch(Exception ex) + catch (Exception ex) { throw ex; } diff --git a/src/Authentication/Authentication/Common/GraphSessionInitializer.cs b/src/Authentication/Authentication/Common/GraphSessionInitializer.cs index 393ea82d12a..13efbf5c7d2 100644 --- a/src/Authentication/Authentication/Common/GraphSessionInitializer.cs +++ b/src/Authentication/Authentication/Common/GraphSessionInitializer.cs @@ -2,8 +2,14 @@ // Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information. // ------------------------------------------------------------------------------ +using System; +using Microsoft.Graph.PowerShell.Authentication.Helpers; +using Microsoft.Graph.PowerShell.Authentication.Models; + namespace Microsoft.Graph.PowerShell.Authentication.Common { + using System.Management.Automation; + using Microsoft.Graph.PowerShell.Authentication.Interfaces; public static class GraphSessionInitializer @@ -15,7 +21,6 @@ public static void InitializeSession() { GraphSession.Initialize(() => CreateInstance()); } - /// /// Creates a new instance of a . /// @@ -27,5 +32,44 @@ internal static GraphSession CreateInstance(IDataStore dataStore = null) DataStore = dataStore ?? new DiskDataStore() }; } + /// + /// Initializes . with Output via Cmdlet methods + /// + /// + internal static void InitializeOutput(CustomAsyncCommandRuntime cmdLet) + { + var outputWriter = new PsGraphOutputWriter + { + WriteDebug = cmdLet.WriteDebug, + WriteInformation = (o, strings) => + { + cmdLet.WriteInformation(new InformationRecord(o, strings)); + }, + WriteObject = cmdLet.WriteObject, + WriteVerbose = cmdLet.WriteVerbose, + WriteError = (exception, errorId, errorCategory, targetObject) => + { + var parseResult = Enum.TryParse(errorCategory.ToString(), out ErrorCategory result); + if (!parseResult) + { + result = ErrorCategory.NotSpecified; + } + var errorRecord = new ErrorRecord(exception, errorId, result, targetObject); + cmdLet.WriteError(errorRecord); + } + }; + InitializeOutput(outputWriter); + } + /// + /// Initializes . with Output via Cmdlet methods + /// + /// + internal static void InitializeOutput(IPSGraphOutputWriter outputWriter) + { + GraphSession.Modify(session => + { + session.OutputWriter = outputWriter; + }); + } } } diff --git a/src/Authentication/Authentication/Helpers/AttachDebugger.cs b/src/Authentication/Authentication/Helpers/AttachDebugger.cs index a8d3bbb36a8..abb7da0b862 100644 --- a/src/Authentication/Authentication/Helpers/AttachDebugger.cs +++ b/src/Authentication/Authentication/Helpers/AttachDebugger.cs @@ -16,19 +16,17 @@ internal static void Break(this PSCmdlet invokedCmdLet) { while (!Debugger.IsAttached) { - Console.Error.WriteLine($"Waiting for debugger to attach to process {Process.GetCurrentProcess().Id}"); + invokedCmdLet.WriteWarning($"Waiting for debugger to attach to process {Process.GetCurrentProcess().Id}"); for (var i = 0; i < 50; i++) { if (Debugger.IsAttached) { break; } - Thread.Sleep(100); - Console.Error.Write("."); + invokedCmdLet.WriteProgress(new ProgressRecord(0, "Waiting for Debugger", + "Waiting for Debugger to attach to process")); } - - Console.Error.WriteLine(); } Debugger.Break(); diff --git a/src/Authentication/Authentication/Models/PsGraphOutputWriter.cs b/src/Authentication/Authentication/Models/PsGraphOutputWriter.cs new file mode 100644 index 00000000000..afd6c12bf3a --- /dev/null +++ b/src/Authentication/Authentication/Models/PsGraphOutputWriter.cs @@ -0,0 +1,17 @@ +// ------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information. +// ------------------------------------------------------------------------------ + +namespace Microsoft.Graph.PowerShell.Authentication.Models +{ + using System; + + internal class PsGraphOutputWriter : IPSGraphOutputWriter + { + public Action WriteObject { get; set; } + public Action WriteDebug { get; set; } + public Action WriteError { get; set; } + public Action WriteInformation { get; set; } + public Action WriteVerbose { get; set; } + } +} \ No newline at end of file