Skip to content

Commit 3750fc8

Browse files
committed
Simplifying cmdlet deferral infrastructure for downlevel PowerShell
1 parent 6931a6b commit 3750fc8

File tree

6 files changed

+266
-36
lines changed

6 files changed

+266
-36
lines changed

src/Common/Commands.Common/AzureLongRunningJob.cs

Lines changed: 68 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
// limitations under the License.
1313
// ----------------------------------------------------------------------------------
1414

15+
using Microsoft.WindowsAzure.Commands.Common;
1516
using Microsoft.WindowsAzure.Commands.Utilities.Common;
1617
using System;
1718
using System.Collections;
@@ -31,7 +32,7 @@ public class AzureLongRunningJob<T> : Job, ICommandRuntime where T : AzurePSCmdl
3132
string _status = "Running";
3233
T _cmdlet;
3334
ICommandRuntime _runtime;
34-
ConcurrentQueue<PSStreamObject> _actions = new ConcurrentQueue<PSStreamObject>();
35+
ConcurrentQueue<ShouldMethodStreamItem> _actions = new ConcurrentQueue<ShouldMethodStreamItem>();
3536
bool _shouldConfirm = false;
3637
Action<T> _execute;
3738

@@ -95,7 +96,7 @@ public override string StatusMessage
9596
/// <summary>
9697
/// A queue of actions that must execute on the cmdlet thread
9798
/// </summary>
98-
protected ConcurrentQueue<PSStreamObject> BlockedActions { get { return _actions; } }
99+
internal ConcurrentQueue<ShouldMethodStreamItem> BlockedActions { get { return _actions; } }
99100

100101
/// <summary>
101102
/// Factory method for creating jobs
@@ -106,6 +107,21 @@ public override string StatusMessage
106107
/// <returns>A job tracking the background execution of the cmdlet</returns>
107108
public static AzureLongRunningJob<U> Create<U>(U cmdlet, string command, string name) where U : AzurePSCmdlet
108109
{
110+
if (cmdlet == null)
111+
{
112+
throw new ArgumentNullException(nameof(cmdlet));
113+
}
114+
115+
if (string.IsNullOrWhiteSpace(command))
116+
{
117+
command = cmdlet.GetType().Name;
118+
}
119+
120+
if (string.IsNullOrWhiteSpace(name))
121+
{
122+
name = "Azure Long-Running Job";
123+
}
124+
109125
var job = new Common.AzureLongRunningJob<U>(cmdlet, command, name);
110126
return job;
111127
}
@@ -121,6 +137,26 @@ public static AzureLongRunningJob<U> Create<U>(U cmdlet, string command, string
121137
/// <returns>A job tracking the background execution of the cmdlet</returns>
122138
public static AzureLongRunningJob<U> Create<U>(U cmdlet, string command, string name, Action<U> executor) where U : AzurePSCmdlet
123139
{
140+
if (null == cmdlet)
141+
{
142+
throw new ArgumentNullException(nameof(cmdlet));
143+
}
144+
145+
if (null == executor)
146+
{
147+
throw new ArgumentNullException(nameof(executor));
148+
}
149+
150+
if (string.IsNullOrWhiteSpace(command))
151+
{
152+
command = cmdlet.GetType().Name;
153+
}
154+
155+
if (string.IsNullOrWhiteSpace(name))
156+
{
157+
name = "Azure Long-Running Job";
158+
}
159+
124160
var job = new Common.AzureLongRunningJob<U>(cmdlet, command, name, executor);
125161
return job;
126162
}
@@ -133,18 +169,23 @@ public static AzureLongRunningJob<U> Create<U>(U cmdlet, string command, string
133169
/// <returns>A deep copy fo the cmdlet</returns>
134170
public static U CopyCmdlet<U>(U cmdlet) where U : AzurePSCmdlet
135171
{
172+
if (cmdlet == null)
173+
{
174+
throw new ArgumentNullException(nameof(cmdlet));
175+
}
176+
136177
var returnType = cmdlet.GetType();
137178
var returnValue = Activator.CreateInstance(cmdlet.GetType());
138179
foreach (var property in returnType.GetProperties())
139180
{
140181
if (property.CanWrite && property.CanRead)
141182
{
142-
property.SetValue(returnValue, property.GetValue(cmdlet));
183+
property.SafeCopyValue(source: cmdlet, target: returnValue);
143184
}
144185
}
145186
foreach (var field in returnType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly))
146187
{
147-
field.SetValue(returnValue, field.GetValue(cmdlet));
188+
field.SafeCopyValue(source: cmdlet, target: returnValue);
148189
}
149190

150191
return returnValue as U;
@@ -164,7 +205,8 @@ public virtual void RunJob(object state)
164205
}
165206
catch (Exception ex)
166207
{
167-
Error.Add(new ErrorRecord(ex, ex.Message, ErrorCategory.InvalidOperation, this));
208+
string message = string.Format("The cmdlet failed in background execution. The returned error was '{0}'. Please execute the cmdlet again. You may need to execute this cmdlet synchronously, by omitting the '-AsJob' parameter", ex.Message);
209+
Error.Add(new ErrorRecord(ex, message, ErrorCategory.InvalidOperation, this));
168210
Fail();
169211
}
170212
}
@@ -185,10 +227,10 @@ protected void UnblockJob()
185227
{
186228
var executor = CopyCmdlet(_cmdlet);
187229
executor.CommandRuntime = _runtime;
188-
PSStreamObject stream;
230+
ShouldMethodStreamItem stream;
189231
while (_actions.TryDequeue(out stream))
190232
{
191-
stream.WriteStreamObject(executor);
233+
stream.ExecuteShouldMethod(executor);
192234
}
193235
}
194236

@@ -266,7 +308,7 @@ public PSHost Host
266308
public bool ShouldContinue(string query, string caption)
267309
{
268310
Exception thrownException;
269-
return InvokeCmdletMethodAndWaitForResults(cmdlet => cmdlet.ShouldContinue(query, caption), out thrownException);
311+
return InvokeShouldMethodAndWaitForResults(cmdlet => cmdlet.ShouldContinue(query, caption), out thrownException);
270312
}
271313

272314
/// <summary>
@@ -282,7 +324,7 @@ public bool ShouldContinue(string query, string caption, ref bool yesToAll, ref
282324
Exception thrownException;
283325
bool localYesToAll = yesToAll;
284326
bool localNoToAll = noToAll;
285-
bool result = InvokeCmdletMethodAndWaitForResults(cmdlet => cmdlet.ShouldContinue(query, caption,
327+
bool result = InvokeShouldMethodAndWaitForResults(cmdlet => cmdlet.ShouldContinue(query, caption,
286328
ref localYesToAll, ref localNoToAll), out thrownException);
287329
yesToAll = localYesToAll;
288330
noToAll = localNoToAll;
@@ -296,9 +338,9 @@ public bool ShouldContinue(string query, string caption, ref bool yesToAll, ref
296338
/// <returns>True if ShouldProcess must be called on the cmdlet thread, otherwise false</returns>
297339
static bool ShouldConfirm(AzurePSCmdlet cmdlet)
298340
{
299-
ConfirmImpact confirmPreference = ConfirmImpact.Medium;
341+
ConfirmImpact confirmPreference = ConfirmImpact.High;
300342
ConfirmImpact cmdletImpact = ConfirmImpact.Medium;
301-
var confirmVar = SafeGetVariableValue(cmdlet, "ConfirmPreference", "Medium");
343+
var confirmVar = SafeGetVariableValue(cmdlet, "ConfirmPreference", "High");
302344
if (!string.IsNullOrEmpty(confirmVar))
303345
{
304346
Enum.TryParse(confirmVar, out confirmPreference);
@@ -310,7 +352,7 @@ static bool ShouldConfirm(AzurePSCmdlet cmdlet)
310352
cmdletImpact = attribute.ConfirmImpact;
311353
}
312354

313-
return cmdletImpact > confirmPreference;
355+
return cmdletImpact >= confirmPreference;
314356
}
315357

316358
static string SafeGetVariableValue(PSCmdlet cmdlet, string name, string defaultValue)
@@ -352,7 +394,7 @@ public bool ShouldProcess(string target)
352394
}
353395

354396
Exception thrownException;
355-
return InvokeCmdletMethodAndWaitForResults(cmdlet => cmdlet.ShouldProcess(target), out thrownException);
397+
return InvokeShouldMethodAndWaitForResults(cmdlet => cmdlet.ShouldProcess(target), out thrownException);
356398
}
357399

358400
/// <summary>
@@ -369,7 +411,7 @@ public bool ShouldProcess(string target, string action)
369411
}
370412

371413
Exception thrownException;
372-
return InvokeCmdletMethodAndWaitForResults(cmdlet => cmdlet.ShouldProcess(target, action), out thrownException);
414+
return InvokeShouldMethodAndWaitForResults(cmdlet => cmdlet.ShouldProcess(target, action), out thrownException);
373415
}
374416

375417
/// <summary>
@@ -387,7 +429,7 @@ public bool ShouldProcess(string verboseDescription, string verboseWarning, stri
387429
}
388430

389431
Exception thrownException;
390-
return InvokeCmdletMethodAndWaitForResults(cmdlet => cmdlet.ShouldProcess(verboseDescription, verboseWarning, caption),
432+
return InvokeShouldMethodAndWaitForResults(cmdlet => cmdlet.ShouldProcess(verboseDescription, verboseWarning, caption),
391433
out thrownException);
392434
}
393435

@@ -409,7 +451,7 @@ public bool ShouldProcess(string verboseDescription, string verboseWarning, stri
409451

410452
ShouldProcessReason closureShouldProcessReason = ShouldProcessReason.None;
411453
Exception thrownException;
412-
bool result = InvokeCmdletMethodAndWaitForResults(cmdlet => cmdlet.ShouldProcess(verboseDescription, verboseWarning, caption, out closureShouldProcessReason),
454+
bool result = InvokeShouldMethodAndWaitForResults(cmdlet => cmdlet.ShouldProcess(verboseDescription, verboseWarning, caption, out closureShouldProcessReason),
413455
out thrownException);
414456
shouldProcessReason = closureShouldProcessReason;
415457
return result;
@@ -431,7 +473,7 @@ public void ThrowTerminatingError(ErrorRecord errorRecord)
431473
public bool TransactionAvailable()
432474
{
433475
Exception thrownException;
434-
return InvokeCmdletMethodAndWaitForResults(cmdlet => cmdlet.TransactionAvailable(),
476+
return InvokeShouldMethodAndWaitForResults(cmdlet => cmdlet.TransactionAvailable(),
435477
out thrownException);
436478
}
437479

@@ -543,13 +585,13 @@ internal bool IsTerminalState(JobState state)
543585
/// Queue actions that must occur on the cmdlet thread, and block the current thread until they are completed
544586
/// </summary>
545587
/// <typeparam name="V">The output type of the called cmdlet action</typeparam>
546-
/// <param name="invokeCmdletMethodAndReturnResult">The action to invoke</param>
588+
/// <param name="shouldMethod">The action to invoke</param>
547589
/// <param name="exceptionThrownOnCmdletThread">Any exception that results</param>
548590
/// <returns>The result of executing the action on the cmdlet thread</returns>
549-
private V InvokeCmdletMethodAndWaitForResults<V>(Func<Cmdlet, V> invokeCmdletMethodAndReturnResult, out Exception exceptionThrownOnCmdletThread)
591+
private bool InvokeShouldMethodAndWaitForResults(Func<Cmdlet, bool> shouldMethod, out Exception exceptionThrownOnCmdletThread)
550592
{
551593

552-
V methodResult = default(V);
594+
bool methodResult = false;
553595
Exception closureSafeExceptionThrownOnCmdletThread = null;
554596
object resultsLock = new object();
555597
using (var gotResultEvent = new ManualResetEventSlim(false))
@@ -580,28 +622,23 @@ private V InvokeCmdletMethodAndWaitForResults<V>(Func<Cmdlet, V> invokeCmdletMet
580622
if (!gotResultEvent.IsSet)
581623
{
582624
this.Block();
583-
// addition to results column happens here
584-
CmdletMethodInvoker<V> methodInvoker = new CmdletMethodInvoker<V>
625+
626+
ShouldMethodInvoker methodInvoker = new ShouldMethodInvoker
585627
{
586-
Action = invokeCmdletMethodAndReturnResult,
628+
ShouldMethod = shouldMethod,
587629
Finished = gotResultEvent,
588630
SyncObject = resultsLock
589631
};
590-
PSStreamObjectType objectType = PSStreamObjectType.ShouldMethod;
591-
if (typeof(V) == typeof(object))
592-
{
593-
objectType = PSStreamObjectType.BlockingError;
594-
}
595632

596-
BlockedActions.Enqueue(new PSStreamObject(objectType, methodInvoker));
633+
BlockedActions.Enqueue(new ShouldMethodStreamItem(methodInvoker));
597634
gotResultEvent.Wait();
598635
this.Start();
599636

600637
lock (resultsLock)
601638
{
602639
if (closureSafeExceptionThrownOnCmdletThread == null) // stateChangedEventHandler didn't set the results? = ok to clobber results?
603640
{
604-
closureSafeExceptionThrownOnCmdletThread = methodInvoker.ExceptionThrownOnCmdletThread;
641+
closureSafeExceptionThrownOnCmdletThread = methodInvoker.ThrownException;
605642
methodResult = methodInvoker.MethodResult;
606643
}
607644
}
@@ -622,7 +659,7 @@ private V InvokeCmdletMethodAndWaitForResults<V>(Func<Cmdlet, V> invokeCmdletMet
622659

623660
public override void StopJob()
624661
{
625-
PSStreamObject stream;
662+
ShouldMethodStreamItem stream;
626663
while (_actions.TryDequeue(out stream));
627664
this.Cancel();
628665
}

src/Common/Commands.Common/Commands.Common.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,11 +122,13 @@
122122
<Reference Include="System.Xml" />
123123
</ItemGroup>
124124
<ItemGroup>
125+
<Compile Include="ShouldMethodInvoker.cs" />
125126
<Compile Include="AzureLongRunningJob.cs" />
126127
<Compile Include="Serialization\LegacyAzureAccount.cs" />
127128
<Compile Include="Serialization\LegacyAzureEnvironment.cs" />
128129
<Compile Include="Serialization\LegacyAzureSubscription.cs" />
129130
<Compile Include="Serialization\ModelConversionExtensions.cs" />
131+
<Compile Include="ShouldMethodStreamItem.cs" />
130132
<Compile Include="Utilities\INetworkHelper.cs" />
131133
<Compile Include="Utilities\JsonUtilities.cs" />
132134
<Compile Include="AzureDataCmdlet.cs" />

0 commit comments

Comments
 (0)