234

I want to prompt the user for a series of inputs, including a password and a filename.

I have an example of using host.ui.prompt, which seems sensible, but I can't understand the return.

Is there a better way to get user input in PowerShell?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
AJ.
  • 13,461
  • 19
  • 51
  • 63

4 Answers4

378

Read-Host is a simple option for getting string input from a user.

$name = Read-Host 'What is your username?'

To hide passwords you can use:

$pass = Read-Host 'What is your password?' -AsSecureString

To convert the password to plain text:

[Runtime.InteropServices.Marshal]::PtrToStringAuto(
    [Runtime.InteropServices.Marshal]::SecureStringToBSTR($pass))

As for the type returned by $host.UI.Prompt(), if you run the code at the link posted in @Christian's comment, you can find out the return type by piping it to Get-Member (for example, $results | gm). The result is a Dictionary where the key is the name of a FieldDescription object used in the prompt. To access the result for the first prompt in the linked example you would type: $results['String Field'].

To access information without invoking a method, leave the parentheses off:

PS> $Host.UI.Prompt

MemberType          : Method
OverloadDefinitions : {System.Collections.Generic.Dictionary[string,psobject] Pr
                    ompt(string caption, string message, System.Collections.Ob
                    jectModel.Collection[System.Management.Automation.Host.Fie
                    ldDescription] descriptions)}
TypeNameOfValue     : System.Management.Automation.PSMethod
Value               : System.Collections.Generic.Dictionary[string,psobject] Pro
                    mpt(string caption, string message, System.Collections.Obj
                    ectModel.Collection[System.Management.Automation.Host.Fiel
                    dDescription] descriptions)
Name                : Prompt
IsInstance          : True

$Host.UI.Prompt.OverloadDefinitions will give you the definition(s) of the method. Each definition displays as <Return Type> <Method Name>(<Parameters>).

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Rynant
  • 23,153
  • 5
  • 57
  • 71
  • Thanks, @Rynant. Accepted answer for being the only one who actually answered my main question! ;) All the other information is really helpful too, especially as I'm still groping my way in PS. – AJ. Nov 23 '11 at 12:38
  • No problem, @AJ. Another way to get information about a method is to leave off the parentheses. I'll add an example to my answer. – Rynant Nov 23 '11 at 14:09
  • 3
    FYI you can also use Get-Credential if you're getting usernames and passwords. – Matt Lyons-Wood Feb 03 '15 at 04:07
76

Using parameter binding is definitely the way to go here. Not only is it very quick to write (just add [Parameter(Mandatory=$true)] above your mandatory parameters), but it's also the only option that you won't hate yourself for later.

More below:

[Console]::ReadLine is explicitly forbidden by the FxCop rules for PowerShell. Why? Because it only works in PowerShell.exe, not PowerShell ISE, PowerGUI, etc.

Read-Host is, quite simply, bad form. Read-Host uncontrollably stops the script to prompt the user, which means that you can never have another script that includes the script that uses Read-Host.

You're trying to ask for parameters.

You should use the [Parameter(Mandatory=$true)] attribute, and correct typing, to ask for the parameters.

If you use this on a [SecureString], it will prompt for a password field. If you use this on a Credential type, ([Management.Automation.PSCredential]), the credentials dialog will pop up, if the parameter isn't there. A string will just become a plain old text box. If you add a HelpMessage to the parameter attribute (that is, [Parameter(Mandatory = $true, HelpMessage = 'New User Credentials')]) then it will become help text for the prompt.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Start-Automating
  • 8,067
  • 2
  • 28
  • 47
  • 6
    This is the most flexible and user-friendly solution, but I almost ignored your advice because there were no clear code examples like in [Rynant's](http://stackoverflow.com/a/8184861/111424) answer. Can you provide some nicely formatted examples? – Iain Samuel McLean Elder Oct 22 '13 at 15:31
  • 5
    "Read-Host is, quite simply, bad form"... unless you're using it to conditionally accept input that was left out because someone wasn't calling your script with ANY parameters. BOOM. –  Aug 26 '14 at 18:41
  • 2
    No: it's still bad form then. That's why you mark parameters as mandatory. – Start-Automating Sep 02 '14 at 21:03
  • 2
    What if you *want* to write an interactive script? Say it is a script that only requires user input if certain conditions are met. For instance if your script is to set up a target directory for an SDK, you might want to confirm that the user wants to delete the directory if it already exists. – Jason Goemaat Mar 09 '15 at 06:11
  • 1
    If you want to confirm an operation, then you should use .ShouldProcess("thing") (along with [CmdletBinding(SupportsShouldProcess=$true)]. This lets you do -WhatIf as well, and Yes/No to All. If you really want to write an interactive script, you're welcome to it (hell, I write a WPF platform and a web lang in PSH, I write lots of interactive scripts), but that interactive script should generally wrap a non-interactive PSH function. The bad that is writing your UI layer at the expense of your automation layer is why PowerShell exists. On the PSH team, it's called "the 30 year hole" – Start-Automating Mar 18 '15 at 06:27
  • 1
    This is by far the best solution and should be upvoted to the top. Using this gives a a much better, better integrated and much less problematic solution to the issue. At the very least there is less code to write with this approach. @user - Not sure what you're suggesting "conditionally accept input that was left out because someone wasn't calling your script with ANY parameters. BOOM." sounds pretty arrogant for such a poorly explained comment. Read-Host is definitely bad form, it requires more user work and will freeze up subscripts. – cchamberlain Sep 27 '15 at 02:49
  • Can this solution cover a use case where the script requires a list of files and a password? I'm not sure how it can work in conjunction with `ValueFromRemainingArguments=$true`. – vmrob Nov 10 '15 at 04:42
  • 6
    I think user1499731 had a good point ... There are times when you need to take input from the user that can only be meaningfully provided *after* some information is displayed or another operation is performed. In that case, you can't use a parameter, and the reasons given here for `Read-Host` being "bad form" don't apply. Moreover, `.ShouldProcess()` has restrictions that `Read-Host` doesn't, such as being limited to just a few answers. However I agree that `.ShouldProcess()` is better, when it's applicable. – LarsH Feb 19 '16 at 11:40
15

Place this at the top of your script. It will cause the script to prompt the user for a password. The resulting password can then be used elsewhere in your script via $pw.

   Param(
     [Parameter(Mandatory=$true, Position=0, HelpMessage="Password?")]
     [SecureString]$password
   )

   $pw = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($password))

If you want to debug and see the value of the password you just read, use:

   write-host $pw
ANeves
  • 6,219
  • 3
  • 39
  • 63
user2334160
  • 415
  • 4
  • 6
3

As an alternative, you could add it as a script parameter for input as part of script execution

 param(
      [Parameter(Mandatory = $True,valueFromPipeline=$true)][String] $value1,
      [Parameter(Mandatory = $True,valueFromPipeline=$true)][String] $value2
      )