33// ------------------------------------------------------------------------------
44namespace Microsoft . Graph . PowerShell . Authentication . Cmdlets
55{
6- using Microsoft . Graph . Auth ;
7- using Microsoft . Graph . PowerShell . Authentication . Helpers ;
8- using Microsoft . Graph . PowerShell . Authentication . Models ;
9- using Microsoft . Identity . Client ;
106 using System ;
117 using System . Collections . Generic ;
128 using System . Linq ;
@@ -16,10 +12,20 @@ namespace Microsoft.Graph.PowerShell.Authentication.Cmdlets
1612 using System . Threading . Tasks ;
1713 using System . Net ;
1814 using System . Globalization ;
19- using Microsoft . Graph . PowerShell . Authentication . Interfaces ;
20- using Microsoft . Graph . PowerShell . Authentication . Common ;
15+ using System . Collections ;
2116 using System . Security . Cryptography . X509Certificates ;
2217
18+ using Identity . Client ;
19+
20+ using Microsoft . Graph . Auth ;
21+ using Microsoft . Graph . PowerShell . Authentication . Helpers ;
22+ using Microsoft . Graph . PowerShell . Authentication . Models ;
23+
24+ using Interfaces ;
25+ using Common ;
26+
27+ using static Helpers . AsyncHelpers ;
28+
2329 [ Cmdlet ( VerbsCommunications . Connect , "MgGraph" , DefaultParameterSetName = Constants . UserParameterSet ) ]
2430 [ Alias ( "Connect-Graph" ) ]
2531 public class ConnectMgGraph : PSCmdlet , IModuleAssemblyInitializer , IModuleAssemblyCleanup
@@ -70,10 +76,12 @@ public class ConnectMgGraph : PSCmdlet, IModuleAssemblyInitializer, IModuleAssem
7076 [ Alias ( "EnvironmentName" , "NationalCloud" ) ]
7177 public string Environment { get ; set ; }
7278
73- [ Parameter ( ParameterSetName = Constants . AppParameterSet , Mandatory = false , HelpMessage = "An x509 Certificate supplied during invocation" ) ]
79+ [ Parameter ( Mandatory = false ,
80+ ParameterSetName = Constants . AppParameterSet ,
81+ HelpMessage = "An x509 Certificate supplied during invocation" ) ]
7482 public X509Certificate2 Certificate { get ; set ; }
7583
76- private CancellationTokenSource cancellationTokenSource ;
84+ private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource ( ) ;
7785
7886 private IGraphEnvironment environment ;
7987
@@ -104,66 +112,102 @@ protected override void EndProcessing()
104112 protected override void ProcessRecord ( )
105113 {
106114 base . ProcessRecord ( ) ;
107- IAuthContext authContext = new AuthContext { TenantId = TenantId } ;
108- cancellationTokenSource = new CancellationTokenSource ( ) ;
109- // Set selected environment to the session object.
110- GraphSession . Instance . Environment = environment ;
111-
112- switch ( ParameterSetName )
115+ try
113116 {
114- case Constants . UserParameterSet :
115- {
116- // 2 mins timeout. 1 min < HTTP timeout.
117- TimeSpan authTimeout = new TimeSpan ( 0 , 0 , Constants . MaxDeviceCodeTimeOut ) ;
118- cancellationTokenSource = new CancellationTokenSource ( authTimeout ) ;
119- authContext . AuthType = AuthenticationType . Delegated ;
120- string [ ] processedScopes = ProcessScopes ( Scopes ) ;
121- authContext . Scopes = processedScopes . Length == 0 ? new string [ ] { "User.Read" } : processedScopes ;
122- // Default to CurrentUser but allow the customer to change this via `ContextScope` param.
123- authContext . ContextScope = this . IsParameterBound ( nameof ( ContextScope ) ) ? ContextScope : ContextScope . CurrentUser ;
124- }
125- break ;
126- case Constants . AppParameterSet :
117+ using ( var asyncCommandRuntime = new CustomAsyncCommandRuntime ( this , _cancellationTokenSource . Token ) )
118+ {
119+ asyncCommandRuntime . Wait ( ProcessRecordAsync ( ) , _cancellationTokenSource . Token ) ;
120+ }
121+ }
122+ catch ( AggregateException aggregateException )
123+ {
124+ // unroll the inner exceptions to get the root cause
125+ foreach ( var innerException in aggregateException . Flatten ( ) . InnerExceptions )
126+ {
127+ var errorRecords = innerException . Data ;
128+ if ( errorRecords . Count < 1 )
127129 {
128- authContext . AuthType = AuthenticationType . AppOnly ;
129- authContext . ClientId = ClientId ;
130- authContext . CertificateThumbprint = CertificateThumbprint ;
131- authContext . CertificateName = CertificateName ;
132- authContext . Certificate = Certificate ;
133- // Default to Process but allow the customer to change this via `ContextScope` param.
134- authContext . ContextScope = this . IsParameterBound ( nameof ( ContextScope ) ) ? ContextScope : ContextScope . Process ;
130+ foreach ( DictionaryEntry dictionaryEntry in errorRecords )
131+ {
132+ WriteError ( ( ErrorRecord ) dictionaryEntry . Value ) ;
133+ }
135134 }
136- break ;
137- case Constants . AccessTokenParameterSet :
135+ else
138136 {
139- authContext . AuthType = AuthenticationType . UserProvidedAccessToken ;
140- authContext . ContextScope = ContextScope . Process ;
141- // Store user provided access token to a session object.
142- GraphSession . Instance . UserProvidedToken = new NetworkCredential ( string . Empty , AccessToken ) . SecurePassword ;
137+ WriteError ( new ErrorRecord ( innerException , string . Empty , ErrorCategory . NotSpecified , null ) ) ;
143138 }
144- break ;
139+ }
145140 }
141+ catch ( Exception exception ) when ( exception as PipelineStoppedException == null ||
142+ ( exception as PipelineStoppedException ) . InnerException != null )
143+ {
144+ // Write exception out to error channel.
145+ WriteError ( new ErrorRecord ( exception , string . Empty , ErrorCategory . NotSpecified , null ) ) ;
146+ }
147+ }
146148
147- CancellationToken cancellationToken = cancellationTokenSource . Token ;
148-
149- try
149+ private async Task ProcessRecordAsync ( )
150+ {
151+ using ( NoSynchronizationContext )
150152 {
151- // Gets a static instance of IAuthenticationProvider when the client app hasn't changed.
152- IAuthenticationProvider authProvider = AuthenticationHelpers . GetAuthProvider ( authContext ) ;
153- IClientApplicationBase clientApplication = null ;
154- if ( ParameterSetName == Constants . UserParameterSet )
153+ IAuthContext authContext = new AuthContext { TenantId = TenantId } ;
154+ // Set selected environment to the session object.
155+ GraphSession . Instance . Environment = environment ;
156+
157+ switch ( ParameterSetName )
155158 {
156- clientApplication = ( authProvider as DeviceCodeProvider ) . ClientApplication ;
159+ case Constants . UserParameterSet :
160+ {
161+ // 2 mins timeout. 1 min < HTTP timeout.
162+ TimeSpan authTimeout = new TimeSpan ( 0 , 0 , Constants . MaxDeviceCodeTimeOut ) ;
163+ // To avoid re-initializing the tokenSource, use CancelAfter
164+ _cancellationTokenSource . CancelAfter ( authTimeout ) ;
165+ authContext . AuthType = AuthenticationType . Delegated ;
166+ string [ ] processedScopes = ProcessScopes ( Scopes ) ;
167+ authContext . Scopes = processedScopes . Length == 0 ? new string [ ] { "User.Read" } : processedScopes ;
168+ // Default to CurrentUser but allow the customer to change this via `ContextScope` param.
169+ authContext . ContextScope = this . IsParameterBound ( nameof ( ContextScope ) ) ? ContextScope : ContextScope . CurrentUser ;
170+ }
171+ break ;
172+ case Constants . AppParameterSet :
173+ {
174+ authContext . AuthType = AuthenticationType . AppOnly ;
175+ authContext . ClientId = ClientId ;
176+ authContext . CertificateThumbprint = CertificateThumbprint ;
177+ authContext . CertificateName = CertificateName ;
178+ authContext . Certificate = Certificate ;
179+ // Default to Process but allow the customer to change this via `ContextScope` param.
180+ authContext . ContextScope = this . IsParameterBound ( nameof ( ContextScope ) ) ? ContextScope : ContextScope . Process ;
181+ }
182+ break ;
183+ case Constants . AccessTokenParameterSet :
184+ {
185+ authContext . AuthType = AuthenticationType . UserProvidedAccessToken ;
186+ authContext . ContextScope = ContextScope . Process ;
187+ // Store user provided access token to a session object.
188+ GraphSession . Instance . UserProvidedToken = new NetworkCredential ( string . Empty , AccessToken ) . SecurePassword ;
189+ }
190+ break ;
157191 }
158- else if ( ParameterSetName == Constants . AppParameterSet )
192+
193+ try
159194 {
160- clientApplication = ( authProvider as ClientCredentialProvider ) . ClientApplication ;
161- }
195+ // Gets a static instance of IAuthenticationProvider when the client app hasn't changed.
196+ IAuthenticationProvider authProvider = AuthenticationHelpers . GetAuthProvider ( authContext ) ;
197+ IClientApplicationBase clientApplication = null ;
198+ if ( ParameterSetName == Constants . UserParameterSet )
199+ {
200+ clientApplication = ( authProvider as DeviceCodeProvider ) . ClientApplication ;
201+ }
202+ else if ( ParameterSetName == Constants . AppParameterSet )
203+ {
204+ clientApplication = ( authProvider as ClientCredentialProvider ) . ClientApplication ;
205+ }
162206
163- // Incremental scope consent without re-instantiating the auth provider. We will use a static instance.
164- GraphRequestContext graphRequestContext = new GraphRequestContext ( ) ;
165- graphRequestContext . CancellationToken = cancellationToken ;
166- graphRequestContext . MiddlewareOptions = new Dictionary < string , IMiddlewareOption >
207+ // Incremental scope consent without re-instantiating the auth provider. We will use a static instance.
208+ GraphRequestContext graphRequestContext = new GraphRequestContext ( ) ;
209+ graphRequestContext . CancellationToken = _cancellationTokenSource . Token ;
210+ graphRequestContext . MiddlewareOptions = new Dictionary < string , IMiddlewareOption >
167211 {
168212 {
169213 typeof ( AuthenticationHandlerOption ) . ToString ( ) ,
@@ -178,49 +222,50 @@ protected override void ProcessRecord()
178222 }
179223 } ;
180224
181- // Trigger consent.
182- HttpRequestMessage httpRequestMessage = new HttpRequestMessage ( HttpMethod . Get , "https://graph.microsoft.com/v1.0/me" ) ;
183- httpRequestMessage . Properties . Add ( typeof ( GraphRequestContext ) . ToString ( ) , graphRequestContext ) ;
184- authProvider . AuthenticateRequestAsync ( httpRequestMessage ) . GetAwaiter ( ) . GetResult ( ) ;
225+ // Trigger consent.
226+ HttpRequestMessage httpRequestMessage = new HttpRequestMessage ( HttpMethod . Get , "https://graph.microsoft.com/v1.0/me" ) ;
227+ httpRequestMessage . Properties . Add ( typeof ( GraphRequestContext ) . ToString ( ) , graphRequestContext ) ;
228+ await authProvider . AuthenticateRequestAsync ( httpRequestMessage ) ;
185229
186- IAccount account = null ;
187- if ( clientApplication != null )
188- {
189- // Only get accounts when we are using MSAL to get an access token.
190- IEnumerable < IAccount > accounts = clientApplication . GetAccountsAsync ( ) . GetAwaiter ( ) . GetResult ( ) ;
191- account = accounts . FirstOrDefault ( ) ;
192- }
193- DecodeJWT ( httpRequestMessage . Headers . Authorization ? . Parameter , account , ref authContext ) ;
230+ IAccount account = null ;
231+ if ( clientApplication != null )
232+ {
233+ // Only get accounts when we are using MSAL to get an access token.
234+ IEnumerable < IAccount > accounts = clientApplication . GetAccountsAsync ( ) . GetAwaiter ( ) . GetResult ( ) ;
235+ account = accounts . FirstOrDefault ( ) ;
236+ }
237+ DecodeJWT ( httpRequestMessage . Headers . Authorization ? . Parameter , account , ref authContext ) ;
194238
195- // Save auth context to session state.
196- GraphSession . Instance . AuthContext = authContext ;
197- }
198- catch ( AuthenticationException authEx )
199- {
200- if ( ( authEx . InnerException is TaskCanceledException ) && cancellationToken . IsCancellationRequested )
239+ // Save auth context to session state.
240+ GraphSession . Instance . AuthContext = authContext ;
241+ }
242+ catch ( AuthenticationException authEx )
201243 {
202- // DeviceCodeTimeout
203- throw new Exception ( string . Format (
204- CultureInfo . CurrentCulture ,
205- ErrorConstants . Message . DeviceCodeTimeout ,
206- Constants . MaxDeviceCodeTimeOut ) ) ;
244+ if ( ( authEx . InnerException is TaskCanceledException ) && _cancellationTokenSource . Token . IsCancellationRequested )
245+ {
246+ // DeviceCodeTimeout
247+ throw new Exception ( string . Format (
248+ CultureInfo . CurrentCulture ,
249+ ErrorConstants . Message . DeviceCodeTimeout ,
250+ Constants . MaxDeviceCodeTimeOut ) ) ;
251+ }
252+ else
253+ {
254+ throw authEx . InnerException ?? authEx ;
255+ }
207256 }
208- else
257+ catch ( Exception ex )
209258 {
210- throw authEx . InnerException ?? authEx ;
259+ throw ex . InnerException ?? ex ;
211260 }
212- }
213- catch ( Exception ex )
214- {
215- throw ex . InnerException ?? ex ;
216- }
217261
218- WriteObject ( "Welcome To Microsoft Graph!" ) ;
262+ WriteObject ( "Welcome To Microsoft Graph!" ) ;
263+ }
219264 }
220265
221266 protected override void StopProcessing ( )
222267 {
223- cancellationTokenSource . Cancel ( ) ;
268+ _cancellationTokenSource . Cancel ( ) ;
224269 base . StopProcessing ( ) ;
225270 }
226271
0 commit comments