Skip to content

Commit c736d90

Browse files
committed
Add Get-Credential support
This change adds initial support for Get-Credential, Read-Host -AsSecureString and any input prompt which uses a SecureString or PSCredential parameter.
1 parent 87ece8a commit c736d90

File tree

2 files changed

+294
-0
lines changed

2 files changed

+294
-0
lines changed
Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
1+
//
2+
// Copyright (c) Microsoft. All rights reserved.
3+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
4+
//
5+
6+
using System;
7+
using System.Collections;
8+
using System.Collections.Generic;
9+
using System.Globalization;
10+
using System.Management.Automation;
11+
using System.Threading;
12+
using System.Threading.Tasks;
13+
14+
namespace Microsoft.PowerShell.EditorServices.Console
15+
{
16+
/// <summary>
17+
/// Provides a base implementation for IPromptHandler classes
18+
/// that present the user a set of fields for which values
19+
/// should be entered.
20+
/// </summary>
21+
public abstract class CredentialPromptHandler : PromptHandler
22+
{
23+
#region Private Fields
24+
25+
private int currentFieldIndex;
26+
private FieldDetails currentField;
27+
private TaskCompletionSource<Dictionary<string, object>> promptTask;
28+
private Dictionary<string, object> fieldValues = new Dictionary<string, object>();
29+
30+
private int currentCollectionIndex;
31+
private FieldDetails currentCollectionField;
32+
private ArrayList currentCollectionItems;
33+
34+
#endregion
35+
36+
#region Properties
37+
38+
/// <summary>
39+
/// Gets the array of fields for which the user must enter values.
40+
/// </summary>
41+
protected FieldDetails[] Fields { get; private set; }
42+
43+
#endregion
44+
45+
#region Public Methods
46+
47+
/// <summary>
48+
/// Prompts the user for a line of input without writing any message or caption.
49+
/// </summary>
50+
/// <returns>
51+
/// A Task instance that can be monitored for completion to get
52+
/// the user's input.
53+
/// </returns>
54+
public Task<string> PromptForInput(
55+
CancellationToken cancellationToken)
56+
{
57+
Task<Dictionary<string, object>> innerTask =
58+
this.PromptForInput(
59+
null,
60+
null,
61+
new FieldDetails[] { new FieldDetails("", "", typeof(string), false, "") },
62+
cancellationToken);
63+
64+
return
65+
innerTask.ContinueWith<string>(
66+
task =>
67+
{
68+
if (task.IsFaulted)
69+
{
70+
throw task.Exception;
71+
}
72+
else if (task.IsCanceled)
73+
{
74+
throw new TaskCanceledException(task);
75+
}
76+
77+
// Return the value of the sole field
78+
return (string)task.Result[""];
79+
});
80+
}
81+
82+
/// <summary>
83+
/// Prompts the user for a line (or lines) of input.
84+
/// </summary>
85+
/// <param name="promptCaption">
86+
/// A title shown before the series of input fields.
87+
/// </param>
88+
/// <param name="promptMessage">
89+
/// A descritpive message shown before the series of input fields.
90+
/// </param>
91+
/// <param name="fields">
92+
/// An array of FieldDetails items to be displayed which prompt the
93+
/// user for input of a specific type.
94+
/// </param>
95+
/// <param name="cancellationToken">
96+
/// A CancellationToken that can be used to cancel the prompt.
97+
/// </param>
98+
/// <returns>
99+
/// A Task instance that can be monitored for completion to get
100+
/// the user's input.
101+
/// </returns>
102+
public Task<Dictionary<string, object>> PromptForInput(
103+
string promptCaption,
104+
string promptMessage,
105+
FieldDetails[] fields,
106+
CancellationToken cancellationToken)
107+
{
108+
this.promptTask = new TaskCompletionSource<Dictionary<string, object>>();
109+
110+
// Cancel the TaskCompletionSource if the caller cancels the task
111+
cancellationToken.Register(this.CancelPrompt, true);
112+
113+
this.Fields = fields;
114+
115+
this.ShowPromptMessage(promptCaption, promptMessage);
116+
this.ShowNextPrompt();
117+
118+
return this.promptTask.Task;
119+
}
120+
121+
/// <summary>
122+
/// Implements behavior to handle the user's response.
123+
/// </summary>
124+
/// <param name="responseString">The string representing the user's response.</param>
125+
/// <returns>
126+
/// True if the prompt is complete, false if the prompt is
127+
/// still waiting for a valid response.
128+
/// </returns>
129+
public override bool HandleResponse(string responseString)
130+
{
131+
if (this.currentField == null)
132+
{
133+
// TODO: Assert
134+
}
135+
136+
// TODO: Is string empty? Use default or finish prompt?
137+
object responseValue = responseString;
138+
139+
try
140+
{
141+
responseValue =
142+
LanguagePrimitives.ConvertTo(
143+
responseString,
144+
this.currentField.FieldType,
145+
CultureInfo.CurrentCulture);
146+
}
147+
catch (PSInvalidCastException e)
148+
{
149+
// Show an error and redisplay the same field
150+
this.ShowErrorMessage(e.InnerException);
151+
this.ShowFieldPrompt(this.currentField);
152+
return false;
153+
}
154+
155+
if (this.currentCollectionField != null)
156+
{
157+
if (responseString.Length == 0)
158+
{
159+
object collection = this.currentCollectionItems;
160+
161+
// Should the result collection be an array?
162+
if (this.currentCollectionField.FieldType.IsArray)
163+
{
164+
// Convert the ArrayList to an array
165+
collection =
166+
this.currentCollectionItems.ToArray(
167+
this.currentCollectionField.ElementType);
168+
}
169+
170+
// Collection entry is done, save the items and clean up state
171+
this.fieldValues.Add(
172+
this.currentCollectionField.Name,
173+
collection);
174+
175+
this.currentField = this.currentCollectionField;
176+
this.currentCollectionField = null;
177+
this.currentCollectionItems = null;
178+
}
179+
else
180+
{
181+
// Add the item to the collection
182+
this.currentCollectionItems.Add(responseValue);
183+
}
184+
}
185+
else
186+
{
187+
this.fieldValues.Add(this.currentField.Name, responseValue);
188+
}
189+
190+
// If there are no more fields to show the prompt is complete
191+
if (this.ShowNextPrompt() == false)
192+
{
193+
this.promptTask.SetResult(this.fieldValues);
194+
return true;
195+
}
196+
197+
// Prompt is still active
198+
return false;
199+
}
200+
201+
/// <summary>
202+
/// Called when the active prompt should be cancelled.
203+
/// </summary>
204+
protected override void OnPromptCancelled()
205+
{
206+
// Cancel the prompt task
207+
this.promptTask.TrySetCanceled();
208+
}
209+
210+
#endregion
211+
212+
#region Abstract Methods
213+
214+
/// <summary>
215+
/// Called when the prompt caption and message should be
216+
/// displayed to the user.
217+
/// </summary>
218+
/// <param name="caption">The caption string to be displayed.</param>
219+
/// <param name="message">The message string to be displayed.</param>
220+
protected abstract void ShowPromptMessage(string caption, string message);
221+
222+
/// <summary>
223+
/// Called when a prompt should be displayed for a specific
224+
/// input field.
225+
/// </summary>
226+
/// <param name="fieldDetails">The details of the field to be displayed.</param>
227+
protected abstract void ShowFieldPrompt(FieldDetails fieldDetails);
228+
229+
/// <summary>
230+
/// Called when an error should be displayed, such as when the
231+
/// user types in a string with an incorrect format for the
232+
/// current field.
233+
/// </summary>
234+
/// <param name="e">
235+
/// The Exception containing the error to be displayed.
236+
/// </param>
237+
protected abstract void ShowErrorMessage(Exception e);
238+
239+
#endregion
240+
241+
#region Private Methods
242+
243+
private bool ShowNextPrompt()
244+
{
245+
if (this.currentCollectionField != null)
246+
{
247+
// Continuing collection entry
248+
this.currentCollectionIndex++;
249+
this.currentField.Name =
250+
string.Format(
251+
"{0}[{1}]",
252+
this.currentCollectionField.Name,
253+
this.currentCollectionIndex);
254+
}
255+
else
256+
{
257+
// Have we shown all the prompts already?
258+
if (this.currentFieldIndex >= this.Fields.Length)
259+
{
260+
return false;
261+
}
262+
263+
this.currentField = this.Fields[this.currentFieldIndex];
264+
265+
if (this.currentField.IsCollection)
266+
{
267+
this.currentCollectionIndex = 0;
268+
this.currentCollectionField = this.currentField;
269+
this.currentCollectionItems = new ArrayList();
270+
271+
this.currentField =
272+
new FieldDetails(
273+
string.Format(
274+
"{0}[{1}]",
275+
this.currentCollectionField.Name,
276+
this.currentCollectionIndex),
277+
this.currentCollectionField.Label,
278+
this.currentCollectionField.ElementType,
279+
true,
280+
null);
281+
}
282+
283+
this.currentFieldIndex++;
284+
}
285+
286+
this.ShowFieldPrompt(this.currentField);
287+
return true;
288+
}
289+
290+
#endregion
291+
}
292+
}

src/PowerShellEditorServices/Console/IPromptHandlerContext.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ public interface IPromptHandlerContext
2828
/// A new InputPromptHandler instance.
2929
/// </returns>
3030
InputPromptHandler GetInputPromptHandler();
31+
32+
CredentialPromptHandler GetCredentialPromptHandler();
3133
}
3234
}
3335

0 commit comments

Comments
 (0)