When working with PowerShell scripts that require sensitive input like passwords, developers often face a dilemma: how to properly handle mandatory SecureString parameters while maintaining security and user experience. The common approach of using Read-Host
directly in parameter declarations presents technical challenges.
PowerShell doesn't allow cmdlet execution within parameter default value assignments. This means code like:
[string]$password = Read-Host "Type password" -AsSecureString
will fail because default values must be constant expressions.
Here are three robust approaches to handle this scenario:
1. Parameter Validation Approach
Param (
[Parameter(Mandatory=$true)]
[string]$FileLocation,
[Parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[Security.SecureString]$Password
)
if (-not $Password) {
$Password = Read-Host "Enter password" -AsSecureString
}
2. Dynamic Parameter Approach
function Get-UserCredential {
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)]
[string]$FileLocation,
[Parameter(Mandatory=$false)]
[Security.SecureString]$Password
)
process {
if (-not $PSBoundParameters.ContainsKey('Password')) {
$Password = Read-Host "Enter password" -AsSecureString
}
# Rest of your logic
}
}
3. Advanced Function with Prompt
function Set-UserPassword {
[CmdletBinding(DefaultParameterSetName='Prompt')]
param(
[Parameter(Mandatory=$true)]
[string]$FileLocation,
[Parameter(Mandatory=$true, ParameterSetName='Direct')]
[Security.SecureString]$Password,
[Parameter(ParameterSetName='Prompt')]
[switch]$PromptForPassword
)
begin {
if ($PSCmdlet.ParameterSetName -eq 'Prompt') {
$Password = Read-Host "Enter password" -AsSecureString
}
}
}
- Always use
-AsSecureString
for password input - Consider using
ConvertTo-SecureString
andConvertFrom-SecureString
for persistence - Avoid converting secure strings to plain text unnecessarily
- Use the
[Credential]
attribute when working with PSCredential objects
function New-SecureUser {
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)]
[string]$Username,
[Parameter(Mandatory=$false)]
[Security.SecureString]$Password,
[Parameter(Mandatory=$true)]
[string]$OUPath
)
process {
if (-not $PSBoundParameters.ContainsKey('Password')) {
$Password = Read-Host "Enter password for $Username" -AsSecureString
$Confirm = Read-Host "Confirm password" -AsSecureString
$passPlain = [Runtime.InteropServices.Marshal]::PtrToStringAuto(
[Runtime.InteropServices.Marshal]::SecureStringToBSTR($Password))
$confirmPlain = [Runtime.InteropServices.Marshal]::PtrToStringAuto(
[Runtime.InteropServices.Marshal]::SecureStringToBSTR($Confirm))
if ($passPlain -ne $confirmPlain) {
throw "Passwords do not match"
}
}
# User creation logic here
}
}
- Make SecureString parameters optional rather than mandatory
- Provide clear prompts when interactive input is needed
- Implement password confirmation for critical operations
- Document your parameter requirements clearly in comment-based help
- Consider using PSCredential objects for better integration with other cmdlets
When working with PowerShell scripts that require sensitive input like passwords, you might want to use Read-Host
with the -AsSecureString
parameter to securely capture user input. However, there's a common challenge when trying to use this directly in a mandatory parameter declaration.
Consider this typical parameter declaration:
Param (
[Parameter(Mandatory=$True)]
[string]$FileLocation,
[Parameter(Mandatory=$True)]
[string]$password = Read-Host "Type the password you would like to set all the users to" -assecurestring
)
The issue here is that you can't directly use Read-Host
in the parameter default value assignment because:
- Parameter attributes are evaluated at parse time, not runtime
- Default values must be constant expressions
- The secure string conversion complicates matters
Here are two effective approaches to handle this scenario:
Solution 1: Separate Parameter Validation
Param (
[Parameter(Mandatory=$True)]
[string]$FileLocation,
[Parameter(Mandatory=$False)]
[securestring]$Password
)
if (-not $Password) {
$Password = Read-Host "Enter password" -AsSecureString
}
Solution 2: Using DynamicParam
For more advanced scenarios, you can use dynamic parameters:
function Get-CredentialInfo {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true)]
[string]$FileLocation
)
DynamicParam {
$paramDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
$attributes = New-Object System.Management.Automation.ParameterAttribute
$attributes.Mandatory = $true
$attributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute]
$attributeCollection.Add($attributes)
$dynParam = New-Object System.Management.Automation.RuntimeDefinedParameter(
"Password",
[securestring],
$attributeCollection
)
$paramDictionary.Add("Password", $dynParam)
return $paramDictionary
}
process {
if (-not $PSBoundParameters.ContainsKey('Password')) {
$PSBoundParameters['Password'] = Read-Host "Enter password" -AsSecureString
}
# Rest of your code
}
}
- Always use
-AsSecureString
for password input - Consider using
Get-Credential
for username/password combinations - Never store passwords in plain text
- Use the
[securestring]
type instead of[string]
for passwords
Here's a complete example showing how to properly handle password input:
Param (
[Parameter(Mandatory=$True)]
[string]$UserListFile,
[Parameter(Mandatory=$False)]
[securestring]$Password
)
# If password not provided, prompt for it
if (-not $Password) {
$Password = Read-Host "Enter default password for new users" -AsSecureString
}
# Convert secure string to plain text (only when absolutely necessary)
$BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($Password)
$PlainPassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)
[System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($BSTR)
# Process user creation
Get-Content $UserListFile | ForEach-Object {
New-ADUser -Name $_ -AccountPassword $Password -Enabled $true
Write-Host "Created user $_"
}
# Clear sensitive data from memory
$PlainPassword = $null
[GC]::Collect()