Prompt for Parameter Value with Default - powershell

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.

Related

How to skip part of PowerShell script by starting script with flags or arguments

I am writing a script that presents the user with a menu of functions, but I also want to be able to run the script automatically from task scheduler which would mean I would need to skip the menu portion. Is there a way to do this with flags or arguments when starting the script (like "script.ps1 -auto" to skip the coding containing the menu, or just "script.ps1" to start)
I've performed internet searches for this, but have not yet found anything that I think is applicable. I'm not even sure if this is possible given the lack of information I've found (or not found).
script.ps1
script.ps1 -auto
Not to the point where error messages are applicable
You can use the [switch] parameter type in your param block.
param( [switch] $auto )
if ($auto) {
# here goes the code if the parameter auto is set
}
else {
}
See also this answer on SO, on how to handle command-line parameters with PowerShell.

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 user input in 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
)

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

How to pass needed parameters to script in Powershell ISE?

See Title.
I specified needed parameters in the head of a script:
param ($G_ARCHIVE = $(throw "Need file to upload!"),
$G_LOGFILE = $(throw "Need logfile!"))
When I want to debug the script with Powershell ISE: how can I fill these parameters?
Use the command pane. Open the script file in the ISE editor, set the breakpoints (F9). Then in the command pane type a command invoking this script with required parameters. I do not think there is another (built-in) way of doing this in ISE.
Open the script (myscript.ps1) in Windows Powershell ISE
Press F9 at the variable you want to inspect (debug). For instance 2nd line in the sample below where the $outputText variable is being assigned
In the shell window provide the relative path of the script along with the param value. For instance: .\myscript.ps1 "my value"
Hit enter (you don't need to hit F5)
You'll be able to see the debugging breakpoints in highlighted with yellow color. Place your cursor to the desired variable to inspect the current value.
There is another way. You can use the $PSDefaultParameterValues automatic variable, which exists (since v3) to provide new default arguments to cmdlets and advanced functions (doesn't work with normal functions). However, it does work for scripts, even when debugging in ISE. You have to declare [CmdletBinding()] or [Parameter()] like you would for an advanced function.
So for your example,
[CmdletBinding()]
param ($G_ARCHIVE = $(throw "Need file to upload!"),
$G_LOGFILE = $(throw "Need logfile!"))
you would execute something like this on the ISE Prompt:
$PSDefaultParameterValues.add("ExampleScript.ps1:G_ARCHIVE","File-to-upload.txt")
$PSDefaultParameterValues.add("ExampleScript.ps1:G_LOGFILE","Example.log")
You could also set the parameter value to a script block which will auto-execute at run-time:
$PSDefaultParameterValues["ExampleScript.ps1:G_LOGFILE"]={
"Example-{0:yyMMddHHmm}.log" -f [datetime]::Now
}
The variable is a hashtable and all the standard syntax applies, except the key must have the name of the script (or advanced function or cmdlet) followed by a colon then the parameter name. You can set defaults for multiple scripts or commands, and multiple parameters for each (each parameter is a new table entry).
By doing it this way, you can just hit F5 to run your script like normal. The parameters will be taken from the variable, so you don't have to type anything in.
Other use cases for $PSDefaultParameterValues might be customizations, like have the Get-History get only the last 10 entries, unless you specify the -Count parameter in the command. Because entries only persist for the current session, you would want to add customizations to your profile. You can read more by typing Get-Help about_Parameters_Default_Values at the prompt or view the same information on TechNet.
There is a much simpler way to set needed Parameters in ISE:
Before pressing F5 in ISE, set the Parameter you need. I usually comment the Parameter I need, example:
# $G_ARCHIVE = "C:\Temp\TestFile_001.txt"
I select everything after "#" and press F8. Next time I debug the script with F5, the Parameter is set to the value I am testing with, no need to pass the Parameters through the command line.
At least in Powershell 5.1 ISE when you press F5 to run a parameterized script, you will be asked to enter values for the parameters one by one.
When using the $PSDefaultParameterValues to populate the variables, you can reference the loaded script through the $psISE variable like
$PSDefaultParameterValues.add("$($psISE.CurrentFile.DisplayName):G_ARCHIVE","test")
I had this problem today. After messing around I discovered that the lower panel was running in Debug E.g. Prompt '[Dbg]: PS'
I stopped the debugging and re-issued my script with paramaeters.
Example: ./myScript.ps1 -ForceBuild $true
My results were that the script started and the breakpoints fired and allowed me to debug as expected.