Prompt for user input in PowerShell - powershell

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?

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>).

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.

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

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
)

Related

Get SecureString as a Plain Text Parameter

I'm trying to get a SecureString as plain text parameter to a command line PowerShell.
I know what is the form of the secure string.
For example, the string "abc" would be a Secure String of "71289371289".
Then, I want to pass "71289371289" as a parameter to the script (Running it from command line), that would be my Secure String and then Decrypt it to a clear text to pass it to another program i'm calling from Powershell.
How would I do something like this?
Update:
I ended up using Credfile with PSCredential to persist the credentials across reboots until the script is complete.
You can convert it back to a clear text password with SecureStringToBSTR:
Param(
$securestring = (Read-Host -AsSecureString)
)
Write-Host "Encrypted Password: $(ConvertFrom-SecureString $securestring)"
$ClearText = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($securestring))
Write-Host "Original Password: $ClearText"

How to bringtofront a messagebox in Powershell

Just need a little help with a Powershell Script.
I have a last messagebox on my script. what i want to accomplish is bring the messagebox in front of all the windows.
cmdlet that i use is
$end=[system.Windows.Forms.Messagebox]::Show('StartUP Tool Progress Completed!','StartUP Warning')
Alternatively, if all you need is a message box you can use the Wscript Shell:
$wshell = New-Object -ComObject Wscript.Shell
$wshell.Popup("StartUP Tool Progress Completed",0,"Completed",0x0)
For more information: Popup Method
This is a WinForms question, more than a PowerShell question. You'll need to pass in Form.ActiveForm. Form.ActiveForm would give you the currently active form, even if you are raising your MessageBox from any other class.
However, I think you might want to look at Read-Host -AsSecureString or, more preferably, Get-Credential if the prompt is for confidential data.
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.
Thankfully, PowerShell has a lot of built-in help for launching dialogs. You're trying to ask for parameters.
You should use the 
[Parameter(Mandatory=$true)]
 attribute, and correct typing, to ask for the parameters. Read up on "params" if you haven't already.
If you use Parameter attribute 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.
Finally, you can try this dirty trick, leveraging Microsoft Visual basic DLLs:
[System.Reflection.Assembly]::LoadWithPartialName('Microsoft.VisualBasic') | Out-Null
$computer = [Microsoft.VisualBasic.Interaction]::InputBox("Enter a computer name", "Computer", "$env:computername")

How do I enforce command line argument checking in PowerShell

Here's my script (test.ps1):
[CmdLetBinding()]
Param
(
[parameter(Mandatory=$true)][string]$environment,
[switch][bool]$continue=$true
)
Write-Host $environment
Write-Host $continue
Question:
If I invoke this script by giving an argument which is a substring of the parameter I specified in the script like this: PS> .\test.ps1 -envi:blah, PowerShell doesn't seem to check the argument name. I want PowerShell to enforce parameter spelling, i.e., it should only accept -environment which matches the parameter name in the script. For anything else, it should raise an exception. Is that doable? How do I do that?
Thanks.
It's not pretty, but it will keep you from using anything except -environment as a parameter name.
Param
(
[parameter(Mandatory=$true)][string]$environment,
[parameter()]
[ValidateScript({throw "Invalid parameter. 'environment' required."})]
[string]$environmen,
[switch][bool]$continue=$true
)
Write-Host $environment
Write-Host $continue
}
Edit: As Matt noted in his comment the automatic disambiguation will force you to specify enough of the parameter name to find a unique substring match. What I'm doing here is basically giving it a parameter that satisfies all but the last character to prevent using any substring up to the last character (because it's ambiguous), and throwing an error to prevent you from using that.
And, FWIW, that could well be the ugliest parameter validation I've ever done but I don't have any better ideas right now.

Prompt for Parameter Value with Default

If I have a parameter like this
[Parameter(Mandatory=$true, ParameterSetName='InsertException')]
[Parameter(Mandatory=$true, ParameterSetName='UpdateException')]
[string]$Requestor = ([Environment]::UserDomainName + '\' + [Environment]::UserName),
When someone runs my script like
.\myscript
the script should prompt him for the value... but it should show a default as well. the user can backspace and remove the default and key in new value.
But what happens is that when powershell prompts the user the value in the console is blank even though I am assigning a default value in the script (as you can see).
So how can I have a default value in the prompt?
No can do. The best I think you can get is to use a HelpMessage like so:
[Parameter(Mandatory=$true,
ParameterSetName='InsertException',
HelpMessage='The default value is foo')]
Then when the user encounters the prompt, they type !? to see the help message.

Using an answer file with a PowerShell script

I have a PowerShell script with a number of 'params' at the start:
param(
[switch] $whatif,
[string] $importPath = $(Read-Host "Full path to import tool"),
[string] $siteUrl = $(Read-Host "Enter URL to create or update"),
[int] $importCount = $(Read-Host "Import number")
)
Is there any way I can run this against an answer file to avoid entering the parameter values every time?
I am not getting the reason for the question. All you have to do to call your script is something like:
.\script.ps1 -whatif -importPath import_path -siteUrl google.com -importCount 1
The Read-Host are there as defaults, to be executed ( and then read and assign the values to the parameters ) only if you don't specify the values. As long you have the above comand ( saved in a file so that you can copy and paste into console or run from another script or whatever ), you don't have to enter the values again and again.
Start by setting the function or script up to accept pipeline input.
[CmdletBinding(SupportsShouldProcess=$True,ConfirmImpact='Low')]
param(
[Parameter(Mandatory=$True,ValueFromPipelineByPropertyName=$True)]
[string] $importPath,
[Parameter(Mandatory=$True,ValueFromPipelineByPropertyName=$True)]
[string] $siteUrl,
[Parameter(Mandatory=$True,ValueFromPipelineByPropertyName=$True)]
[int] $importCount
)
Notice that I removed your manually-created -whatif. No need for it - I'll get to it in a second. Also note that Mandatory=$True will make PowerShell prompt for a value if it isn't provided, so I removed your Read-Host.
Given the above, you could create an "answer file" that is a CSV file. Make an importPath column, a siteURL column, and an importCount column in the CSV file:
importPath,siteURL,importCount
"data","data",1
"x","y",2
Then do this:
Import-CSV my-csv-file.csv | ./My-Script
Assuming your script is My-Script.ps1, of course.
Now, to -whatif. Within the body of your script, do this:
if ($pscmdlet.shouldprocess($target)) {
# do whatever your action is here
}
This assumes you're doing something to $target, which might be a path, a computer name, a URL, or whatever. It's the thing you're modifying in your script. Put your modification actions/commands inside that if construct. Doing this, along with the SupportsShouldProcess() declaration at the top of the script, will enable -whatif and -confirm support. You don't need to code those parameters yourself.
What you're building is called an "Advanced Function," or if it's just a script than I guess it'd be an "Advanced Script." Utilizing pipeline input parameters in this fashion is the "PowerShell way of doing things."
To my knowledge, Powershell doesn't have a built-in understanding of answer files. You'll have to pass them in somehow or read them yourself from the answer file.
Wrapper. You could write another script that calls this script with the same parameters you want to use every time. You could also make a wrapper script that reads the values from the answer file, then pass them in.
Optional Parameters. Or you could change the parameters to use defaults that indicate no parameters were passed, then check for a file of a specific name to read values from. If the file isn't found, then prompt for the values.
If the format of the answer file is flexible, (i.e., you're only going to be using it with this Powershell script), you could get much closer to the behavior of an actual answer file by writing it as a Powershell script itself and dot-sourcing it.
if (test-path 'myAnswerfile'){
. 'myAnswerFile'
#process whatever was sourced from the answer file, if necessary
} else {
#prompt for values
}
It still requires removing the Read-Host calls from the parameters of the script.
Following on from Joel you could set up a different parameter set, based around the switch -answerfile.
If that's set the function will look for an answer file and parse though it - as he said you'll need to do that yourself. If it's not set and the others are then the functionis used with the parameters given. Minor benefit I see is that you can still have the parameters mandatory when used that way.
Matt