Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ namespace Microsoft.PowerShell.Commands
/// Retrieves input from the host virtual console and writes it to the pipeline output.
/// </summary>

[Cmdlet(VerbsCommunications.Read, "Host", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096610")]
[Cmdlet(VerbsCommunications.Read, "Host", DefaultParameterSetName = "AsString", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096610")]
[OutputType(typeof(string), typeof(SecureString))]
public sealed class ReadHostCommand : PSCmdlet
{
Expand Down Expand Up @@ -55,10 +55,9 @@ public sealed class ReadHostCommand : PSCmdlet
}

/// <summary>
/// Set to no echo the input as is is typed.
/// Gets or sets to no echo the input as is is typed. If set then the cmdlet returns a secure string.
/// </summary>

[Parameter]
[Parameter(ParameterSetName = "AsSecureString")]
public
SwitchParameter
AsSecureString
Expand All @@ -73,6 +72,18 @@ public sealed class ReadHostCommand : PSCmdlet
_safe = value;
}
}

/// <summary>
/// Gets or sets whether the console will echo the input as is is typed. If set then the cmdlet returns a regular string.
/// </summary>
[Parameter(ParameterSetName = "AsString")]
public
SwitchParameter
MaskInput
{
get;
set;
}
#endregion Parameters

#region Cmdlet Overrides
Expand Down Expand Up @@ -149,6 +160,10 @@ protected override void BeginProcessing()
{
result = Host.UI.ReadLineAsSecureString();
}
else if (MaskInput)
{
result = Host.UI.ReadLineMaskedAsString();
}
else
{
result = Host.UI.ReadLine();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,17 @@ public override string ReadLine()
throw new PSNotImplementedException();
}

/// <summary>
/// Null implementation of ReadLineMaskedAsString.
/// </summary>
/// <returns>
/// It throws an exception.
/// </returns>
public override string ReadLineMaskedAsString()
{
throw new PSNotImplementedException();
}

/// <summary>
/// ReadLineAsSecureString.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ namespace Microsoft.PowerShell
[SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling")]
internal partial class ConsoleHostUserInterface : System.Management.Automation.Host.PSHostUserInterface
{

/// <summary>
/// This is the char that is echoed to the console when the input is masked. This not localizable.
/// </summary>
private const char PrintToken = '*';
Comment thread
davinci26 marked this conversation as resolved.

/// <summary>
/// Command completion implementation object.
/// </summary>
Expand Down Expand Up @@ -174,6 +180,39 @@ public override string ReadLine()
return ReadLine(false, string.Empty, out unused, true, true);
}

/// <summary>
/// See base class.
/// </summary>
/// <returns>
/// The characters typed by the user.
/// </returns>
/// <exception cref="HostException">
/// If obtaining a handle to the active screen buffer failed
/// OR
/// Win32's setting input buffer mode to disregard window and mouse input failed.
/// OR
/// Win32's ReadConsole failed.
/// </exception>
/// <exception cref="PipelineStoppedException">
/// If Ctrl-C is entered by user.
/// </exception>
public override string ReadLineMaskedAsString()
{
HandleThrowOnReadAndPrompt();

// we lock here so that multiple threads won't interleave the various reads and writes here.
object result = null;
lock (_instanceLock)
{
result = ReadLineSafe(false, PrintToken);
}

StringBuilder resultSb = result as StringBuilder;
Comment thread
davinci26 marked this conversation as resolved.
Dbg.Assert(resultSb != null, "ReadLineMaskedAsString did not return a stringBuilder");

return resultSb.ToString();
}

/// <summary>
/// See base class.
/// </summary>
Expand All @@ -193,14 +232,12 @@ public override SecureString ReadLineAsSecureString()
{
HandleThrowOnReadAndPrompt();

const char printToken = '*'; // This is not localizable

// we lock here so that multiple threads won't interleave the various reads and writes here.

object result = null;
lock (_instanceLock)
{
result = ReadLineSafe(true, printToken);
result = ReadLineSafe(true, PrintToken);
}

SecureString secureResult = result as SecureString;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ public override bool SupportsVirtualTerminal
/// </summary>
/// <exception cref="HostException">
/// if the UI property of the external host is null, possibly because the PSHostUserInterface is not
/// implemented by the external host
/// implemented by the external host.
/// </exception>
public override
string
Expand Down Expand Up @@ -120,12 +120,53 @@ public override
return result;
}

/// <summary>
/// See base class.
/// </summary>
/// <returns>
/// The characters typed by the user.
/// </returns>
/// <exception cref="HostException">
/// If the UI property of the external host is null, possibly because the PSHostUserInterface is not
/// implemented by the external host.
/// </exception>
public override
string
ReadLineMaskedAsString()
{
if (_externalUI == null)
{
ThrowNotInteractive();
}

string result = null;

try
{
result = _externalUI.ReadLineMaskedAsString();
}
catch (PipelineStoppedException)
{
// PipelineStoppedException is thrown by host when it wants
// to stop the pipeline.
LocalPipeline lpl = (LocalPipeline)((RunspaceBase)_parent.Context.CurrentRunspace).GetCurrentlyRunningPipeline();
if (lpl == null)
{
throw;
}

lpl.Stopper.Stop();
}

return result;
}

/// <summary>
/// See base class.
/// </summary>
/// <exception cref="HostException">
/// if the UI property of the external host is null, possibly because the PSHostUserInterface is not
/// implemented by the external host
/// implemented by the external host.
/// </exception>

public override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,30 @@ public abstract System.Management.Automation.Host.PSHostRawUserInterface RawUI
/// <seealso cref="System.Management.Automation.Host.PSHostUserInterface.PromptForChoice"/>
/// <seealso cref="System.Management.Automation.Host.PSHostUserInterface.Prompt"/>
public abstract string ReadLine();

/// <summary>
/// Same as ReadLine except that the input is not echoed to the user while it is collected
/// or is echoed in some obfuscated way, such as showing a dot for each character.
/// </summary>
/// <returns>
/// The characters typed by the user.
/// </returns>
/// <remarks>
/// Note that credentials (a user name and password) should be gathered with
/// <see cref="System.Management.Automation.Host.PSHostUserInterface.PromptForCredential(string, string, string, string)"/>
/// <see cref="System.Management.Automation.Host.PSHostUserInterface.PromptForCredential(string, string, string, string, System.Management.Automation.PSCredentialTypes, System.Management.Automation.PSCredentialUIOptions)"/>
/// </remarks>
/// <seealso cref="System.Management.Automation.Host.PSHostUserInterface.ReadLine"/>
/// <seealso cref="System.Management.Automation.Host.PSHostUserInterface.PromptForCredential(string, string, string, string)"/>
/// <seealso cref="System.Management.Automation.Host.PSHostUserInterface.PromptForCredential(string, string, string, string, System.Management.Automation.PSCredentialTypes, System.Management.Automation.PSCredentialUIOptions)"/>
/// <seealso cref="System.Management.Automation.Host.PSHostUserInterface.PromptForChoice"/>
/// <seealso cref="System.Management.Automation.Host.PSHostUserInterface.Prompt"/>
public virtual string ReadLineMaskedAsString()
{
// Default implementation of the function to maintain backwards compatibility of the base class.
throw new PSNotImplementedException();
}

/// <summary>
/// Same as ReadLine, except that the result is a SecureString, and that the input is not echoed to the user while it is
/// collected (or is echoed in some obfuscated way, such as showing a dot for each character).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,17 @@ public override void WriteWarningLine(string message)
_serverMethodExecutor.ExecuteVoidMethod(RemoteHostMethodId.WriteWarningLine, new object[] { message });
}

/// <summary>
/// Read line as string masked.
/// </summary>
/// <returns>
/// Not implemented. It throws an exception.
/// </returns>
public override string ReadLineMaskedAsString()
{
throw new PSNotImplementedException();
Comment thread
davinci26 marked this conversation as resolved.
}

/// <summary>
/// Read line as secure string.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,17 @@ Describe "Read-Host Test" -tag "CI" {
[pscredential]::New("foo",$result).GetNetworkCredential().Password | Should -BeExactly TEST
}

It "Read-Host returns a string when using -MaskInput parameter" {
$result = $ps.AddScript("Read-Host -MaskInput").Invoke()
$result | Should -Be $th.UI.ReadLineData
}

It "Read-Host throws an error when both -AsSecureString parameter and -MaskInput parameter are used" {
# Contrary to the rest of the tests this does not need to be invoked through a runspace since it is going to throw an error.
$errorId = "AmbiguousParameterSet,Microsoft.PowerShell.Commands.ReadHostCommand"
{Read-Host -MaskInput -AsSecureString} | Should -Throw -ErrorId $errorId
}

It "Read-Host doesn't enter command prompt mode" {
$result = "!1" | pwsh -NoProfile -c "Read-host -Prompt 'foo'"
if ($IsWindows) {
Expand Down
5 changes: 5 additions & 0 deletions test/tools/Modules/HelpersHostCS/HelpersHostCS.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,11 @@ namespace TestHost
return ReadLineData;
}

public override string ReadLineMaskedAsString()
{
return ReadLineData;
}

public override SecureString ReadLineAsSecureString()
{
SecureString ss = new SecureString();
Expand Down