1
- // ----------------------------------------------------------------------------------
1
+ // ----------------------------------------------------------------------------------
2
2
//
3
3
// Copyright Microsoft Corporation
4
4
// Licensed under the Apache License, Version 2.0 (the "License");
21
21
using Microsoft . Azure . Common . Authentication ;
22
22
using Microsoft . Azure . Common . Authentication . Models ;
23
23
using Microsoft . WindowsAzure . Commands . Common ;
24
+ using Microsoft . WindowsAzure . Commands . Common . Properties ;
25
+ using Newtonsoft . Json ;
26
+ using System ;
27
+ using System . Collections . Concurrent ;
28
+ using System . Diagnostics ;
29
+ using System . IO ;
30
+ using System . Management . Automation ;
31
+ using System . Management . Automation . Host ;
32
+ using System . Threading ;
24
33
25
34
namespace Microsoft . WindowsAzure . Commands . Utilities . Common
26
35
{
@@ -34,6 +43,21 @@ public abstract class AzurePSCmdlet : PSCmdlet, IDisposable
34
43
private RecordingTracingInterceptor _httpTracingInterceptor ;
35
44
36
45
private DebugStreamTraceListener _adalListener ;
46
+ protected static AzurePSDataCollectionProfile _dataCollectionProfile = null ;
47
+
48
+ protected AzurePSQoSEvent QosEvent ;
49
+
50
+ protected virtual bool IsUsageMetricEnabled {
51
+ get { return false ; }
52
+ }
53
+
54
+ protected virtual bool IsErrorMetricEnabled
55
+ {
56
+ get { return true ; }
57
+ }
58
+
59
+ [ Parameter ( Mandatory = false , HelpMessage = "In-memory profile." ) ]
60
+ public AzureProfile Profile { get ; set ; }
37
61
38
62
/// <summary>
39
63
/// Gets the PowerShell module name used for user agent header.
@@ -60,10 +84,174 @@ public AzurePSCmdlet()
60
84
}
61
85
62
86
/// <summary>
63
- /// Cmdlet begin process. Write to logs, setup Http Tracing and initialize profile and adds user agent.
87
+ /// Initialize the data collection profile
88
+ /// </summary>
89
+ protected static void InitializeDataCollectionProfile ( )
90
+ {
91
+ if ( _dataCollectionProfile != null && _dataCollectionProfile . EnableAzureDataCollection . HasValue )
92
+ {
93
+ return ;
94
+ }
95
+
96
+ // Get the value of the environment variable for Azure PS data collection setting.
97
+ string value = Environment . GetEnvironmentVariable ( AzurePSDataCollectionProfile . EnvironmentVariableName ) ;
98
+ if ( ! string . IsNullOrWhiteSpace ( value ) )
99
+ {
100
+ if ( string . Equals ( value , bool . FalseString , StringComparison . OrdinalIgnoreCase ) )
101
+ {
102
+ // Disable data collection only if it is explictly set to 'false'.
103
+ _dataCollectionProfile = new AzurePSDataCollectionProfile ( true ) ;
104
+ }
105
+ else if ( string . Equals ( value , bool . TrueString , StringComparison . OrdinalIgnoreCase ) )
106
+ {
107
+ // Enable data collection only if it is explictly set to 'true'.
108
+ _dataCollectionProfile = new AzurePSDataCollectionProfile ( false ) ;
109
+ }
110
+ }
111
+
112
+ // If the environment value is null or empty, or not correctly set, try to read the setting from default file location.
113
+ if ( _dataCollectionProfile == null )
114
+ {
115
+ string fileFullPath = Path . Combine ( AzureSession . ProfileDirectory , AzurePSDataCollectionProfile . DefaultFileName ) ;
116
+ if ( File . Exists ( fileFullPath ) )
117
+ {
118
+ string contents = File . ReadAllText ( fileFullPath ) ;
119
+ _dataCollectionProfile = JsonConvert . DeserializeObject < AzurePSDataCollectionProfile > ( contents ) ;
120
+ }
121
+ }
122
+
123
+ // If the environment variable or file content is not set, create a new profile object.
124
+ if ( _dataCollectionProfile == null )
125
+ {
126
+ _dataCollectionProfile = new AzurePSDataCollectionProfile ( ) ;
127
+ }
128
+ }
129
+
130
+ /// <summary>
131
+ /// Get the data collection profile
132
+ /// </summary>
133
+ protected static AzurePSDataCollectionProfile GetDataCollectionProfile ( )
134
+ {
135
+ if ( _dataCollectionProfile == null )
136
+ {
137
+ InitializeDataCollectionProfile ( ) ;
138
+ }
139
+
140
+ return _dataCollectionProfile ;
141
+ }
142
+
143
+ /// <summary>
144
+ /// Check whether the data collection is opted in from user
145
+ /// </summary>
146
+ /// <returns>true if allowed</returns>
147
+ public static bool IsDataCollectionAllowed ( )
148
+ {
149
+ if ( _dataCollectionProfile != null &&
150
+ _dataCollectionProfile . EnableAzureDataCollection . HasValue &&
151
+ _dataCollectionProfile . EnableAzureDataCollection . Value )
152
+ {
153
+ return true ;
154
+ }
155
+
156
+ return false ;
157
+ }
158
+
159
+ /// <summary>
160
+ /// Save the current data collection profile Json data into the default file path
161
+ /// </summary>
162
+ /// <param name="profile"></param>
163
+ protected void SaveDataCollectionProfile ( )
164
+ {
165
+ if ( _dataCollectionProfile == null )
166
+ {
167
+ InitializeDataCollectionProfile ( ) ;
168
+ }
169
+
170
+ string fileFullPath = Path . Combine ( AzureSession . ProfileDirectory , AzurePSDataCollectionProfile . DefaultFileName ) ;
171
+ var contents = JsonConvert . SerializeObject ( _dataCollectionProfile ) ;
172
+ AzureSession . DataStore . WriteFile ( fileFullPath , contents ) ;
173
+ WriteWarning ( string . Format ( Resources . DataCollectionSaveFileInformation , fileFullPath ) ) ;
174
+ }
175
+
176
+ protected bool CheckIfInteractive ( )
177
+ {
178
+ bool interactive = true ;
179
+ if ( this . Host == null || this . Host . UI == null || this . Host . UI . RawUI == null )
180
+ {
181
+ interactive = false ;
182
+ }
183
+ else
184
+ {
185
+ try
186
+ {
187
+ var test = this . Host . UI . RawUI . KeyAvailable ;
188
+ }
189
+ catch
190
+ {
191
+ interactive = false ;
192
+ }
193
+ }
194
+
195
+ if ( ! interactive && ! _dataCollectionProfile . EnableAzureDataCollection . HasValue )
196
+ {
197
+ _dataCollectionProfile . EnableAzureDataCollection = false ;
198
+ }
199
+ return interactive ;
200
+ }
201
+
202
+ /// <summary>
203
+ /// Prompt for the current data collection profile
204
+ /// </summary>
205
+ /// <param name="profile"></param>
206
+ protected void PromptForDataCollectionProfileIfNotExists ( )
207
+ {
208
+ // Initialize it from the environment variable or profile file.
209
+ InitializeDataCollectionProfile ( ) ;
210
+
211
+ if ( ! _dataCollectionProfile . EnableAzureDataCollection . HasValue && CheckIfInteractive ( ) )
212
+ {
213
+ WriteWarning ( Resources . DataCollectionPrompt ) ;
214
+
215
+ const double timeToWaitInSeconds = 60 ;
216
+ var status = string . Format ( Resources . DataCollectionConfirmTime , timeToWaitInSeconds ) ;
217
+ ProgressRecord record = new ProgressRecord ( 0 , Resources . DataCollectionActivity , status ) ;
218
+
219
+ var startTime = DateTime . Now ;
220
+ var endTime = DateTime . Now ;
221
+ double elapsedSeconds = 0 ;
222
+
223
+ while ( ! this . Host . UI . RawUI . KeyAvailable && elapsedSeconds < timeToWaitInSeconds )
224
+ {
225
+ Thread . Sleep ( TimeSpan . FromMilliseconds ( 10 ) ) ;
226
+ endTime = DateTime . Now ;
227
+
228
+ elapsedSeconds = ( endTime - startTime ) . TotalSeconds ;
229
+ record . PercentComplete = ( ( int ) elapsedSeconds * 100 / ( int ) timeToWaitInSeconds ) ;
230
+ WriteProgress ( record ) ;
231
+ }
232
+
233
+ bool enabled = false ;
234
+ if ( this . Host . UI . RawUI . KeyAvailable )
235
+ {
236
+ KeyInfo keyInfo = this . Host . UI . RawUI . ReadKey ( ReadKeyOptions . NoEcho | ReadKeyOptions . AllowCtrlC | ReadKeyOptions . IncludeKeyDown ) ;
237
+ enabled = ( keyInfo . Character == 'Y' || keyInfo . Character == 'y' ) ;
238
+ }
239
+
240
+ _dataCollectionProfile . EnableAzureDataCollection = enabled ;
241
+
242
+ WriteWarning ( enabled ? Resources . DataCollectionConfirmYes : Resources . DataCollectionConfirmNo ) ;
243
+
244
+ SaveDataCollectionProfile ( ) ;
245
+ }
246
+ }
247
+
248
+ /// <summary>
249
+ /// Cmdlet begin process. Write to logs, setup Http Tracing and initialize profile
64
250
/// </summary>
65
251
protected override void BeginProcessing ( )
66
252
{
253
+ PromptForDataCollectionProfileIfNotExists ( ) ;
254
+ InitializeQosEvent ( ) ;
67
255
if ( string . IsNullOrEmpty ( ParameterSetName ) )
68
256
{
69
257
WriteDebugWithTimestamp ( string . Format ( "{0} begin processing without ParameterSet." , this . GetType ( ) . Name ) ) ;
@@ -95,6 +283,7 @@ protected override void BeginProcessing()
95
283
/// </summary>
96
284
protected override void EndProcessing ( )
97
285
{
286
+ LogQosEvent ( ) ;
98
287
string message = string . Format ( "{0} end processing." , this . GetType ( ) . Name ) ;
99
288
WriteDebugWithTimestamp ( message ) ;
100
289
@@ -125,6 +314,13 @@ protected bool IsVerbose()
125
314
protected new void WriteError ( ErrorRecord errorRecord )
126
315
{
127
316
FlushDebugMessages ( ) ;
317
+ if ( QosEvent != null && errorRecord != null )
318
+ {
319
+ QosEvent . Exception = errorRecord . Exception ;
320
+ QosEvent . IsSuccess = false ;
321
+ LogQosEvent ( true ) ;
322
+ }
323
+
128
324
base . WriteError ( errorRecord ) ;
129
325
}
130
326
@@ -234,6 +430,62 @@ private void FlushDebugMessages()
234
430
}
235
431
}
236
432
433
+ protected void InitializeQosEvent ( )
434
+ {
435
+ QosEvent = new AzurePSQoSEvent ( )
436
+ {
437
+ CmdletType = this . GetType ( ) . Name ,
438
+ IsSuccess = true ,
439
+ } ;
440
+
441
+ if ( this . Profile != null && this . Profile . DefaultSubscription != null )
442
+ {
443
+ QosEvent . Uid = MetricHelper . GenerateSha256HashString (
444
+ this . Profile . DefaultSubscription . Id . ToString ( ) ) ;
445
+ }
446
+ else
447
+ {
448
+ QosEvent . Uid = "defaultid" ;
449
+ }
450
+ }
451
+
452
+ /// <summary>
453
+ /// Invoke this method when the cmdlet is completed or terminated.
454
+ /// </summary>
455
+ protected void LogQosEvent ( bool waitForMetricSending = false )
456
+ {
457
+ if ( QosEvent == null )
458
+ {
459
+ return ;
460
+ }
461
+
462
+ QosEvent . FinishQosEvent ( ) ;
463
+
464
+ if ( ! IsUsageMetricEnabled && ( ! IsErrorMetricEnabled || QosEvent . IsSuccess ) )
465
+ {
466
+ return ;
467
+ }
468
+
469
+ if ( ! IsDataCollectionAllowed ( ) )
470
+ {
471
+ return ;
472
+ }
473
+
474
+ WriteDebug ( QosEvent . ToString ( ) ) ;
475
+
476
+ try
477
+ {
478
+ MetricHelper . LogQoSEvent ( QosEvent , IsUsageMetricEnabled , IsErrorMetricEnabled ) ;
479
+ MetricHelper . FlushMetric ( waitForMetricSending ) ;
480
+ WriteDebug ( "Finish sending metric." ) ;
481
+ }
482
+ catch ( Exception e )
483
+ {
484
+ //Swallow error from Application Insights event collection.
485
+ WriteWarning ( e . ToString ( ) ) ;
486
+ }
487
+ }
488
+
237
489
/// <summary>
238
490
/// Asks for confirmation before executing the action.
239
491
/// </summary>
@@ -244,10 +496,19 @@ private void FlushDebugMessages()
244
496
/// <param name="action">The action code</param>
245
497
protected void ConfirmAction ( bool force , string actionMessage , string processMessage , string target , Action action )
246
498
{
499
+ if ( QosEvent != null )
500
+ {
501
+ QosEvent . PauseQoSTimer ( ) ;
502
+ }
503
+
247
504
if ( force || ShouldContinue ( actionMessage , "" ) )
248
505
{
249
506
if ( ShouldProcess ( target , processMessage ) )
250
- {
507
+ {
508
+ if ( QosEvent != null )
509
+ {
510
+ QosEvent . ResumeQosTimer ( ) ;
511
+ }
251
512
action ( ) ;
252
513
}
253
514
}
0 commit comments