Itchydon
Itchydon

Reputation: 2596

Can Read-Host accept a variable as input?

I would like to use Read-Host -AsSecureString to avoid typing a password into my script. I want to loop through a list of servers and include each server name in the password - so all passwords are different. The script works as I would like if I put the password in the script using a variable, but not if I use the same variable as a value for Read-Host. Can Read-Host -AsSecureString not contain a variable?

See code below. When using the $passwordsecure variable (Read-Host value) the password is literally what was typed i.e. $computername, however $password (which is commented out) produces the expected machinename as part of the password:

<#
Info:
- Launch the script and respond to the on-screen prompts:
  - Enter the local user account housed on the remote servers whose password
    needs to be changed (e.g. administrator or testUser)
  - Enter path to txt file containing server list (e.g. E:Temp\servers.txt)
  - Enter new password
  - The script will connect to each server and change the password of the local
    user account defined
#>

# Changes PS prompt to directory in which the script is being run from whether using Powershell console or the ISE.
function Get-ScriptDirectory {
    $Invocation = (Get-Variable MyInvocation -Scope 1).Value
    Split-Path $Invocation.MyCommand.Path
}

$ISEScriptPath = Split-Path -Parent $psISE.CurrentFile.Fullpath -EA SilentlyContinue

if ($ISEScriptPath -eq $null) {
    cd $(Get-ScriptDirectory -ea SilentlyContinue)
} else {
    cd $ISEScriptPath
}

############## Main Script ###########

$fail = @()
$pass = @()
$passLog = @()
$failLog = @()
$date = "_$(get-date -uf %H%M_%d%h%Y)"
$Results = Test-Path  "$PWD\Results"

if (-not $Results) {
    md "$PWD\Results"
}

$user = Read-Host "Enter local account name whose password is to be changed on remote servers (e.g. testUser)"

Write-Host "N.B. If the .txt file is located in '$PWD' just enter the filename (e.g. servers.txt)"
"`n"
$path = (Read-Host "Enter path to the .txt file containing servers for which the $(($user).toupper()) password will be changed (e.g. e:\temp\servers.txt)")
$computerNames = Get-Content $path
$passwordSecure = Read-Host -AsSecureString "Enter new password"

$password = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($passwordSecure))
#$password = "$($computername)!!"

foreach ($computername in $computernames) {
    $date2 =  (Get-Date).ToString('dd-MM-yyyy hh:mm:ss tt')
    $computername
    $adminUser = [ADSI] "WinNT://$computerName/$user,User"
    $adminUser.SetPassword($Password)

    if ($?) {
        "$computername was successfully updated"
        $PassLog +="$computername updated successfully at $date2"
        $Pass+="`n$computername updated successfully"
        Start-Sleep -m 500
    } else {
        "$computername failed to update"
        $failLog +="$computername failed to update at $date2"
        $fail+="`n$computername failed to update"
        Start-Sleep -m 500
    }
}

"$(($fail).count) $(($user).toupper()) password failed to be reset on following servers and needs to be checked manually..`n";
Write-Host $fail -ForegroundColor "Red"

"`n$(($Pass).count) servers had the $(($user).toupper()) account password updated successfully..`n"
Write-Host $Pass -ForegroundColor "green"
"`n"

                          "Processing servers provided in $PWD\$path...." | Out-File  ("$PWD\Results\" +  $("$user"+"_PasswordReset" + $date + ".log")) -Append
                                                                   "`n`n" | Out-File  ("$PWD\Results\" +  $("$user"+"_PasswordReset" + $date + ".log")) -Append
      "Failed to Reset $(($fail).count) $(($user).toupper()) passwords:`n"| Out-File  ("$PWD\Results\" +  $("$user"+"_PasswordReset" + $date + ".log")) -Append
                                                                     "`n" | Out-File  ("$PWD\Results\" +  $("$user"+"_PasswordReset" + $date + ".log")) -Append
                                                                 $failLog | Out-File  ("$PWD\Results\" +  $("$user"+"_PasswordReset" + $date + ".log")) -Append
                                                                   "`n`n" | Out-File  ("$PWD\Results\" +  $("$user"+"_PasswordReset" + $date + ".log")) -Append
"Successfully Updated $(($Pass).count) $(($user).toupper()) passwords:`n" | Out-File  ("$PWD\Results\" +  $("$user"+"_PasswordReset" + $date + ".log")) -Append
                                                                     "`n" | Out-File  ("$PWD\Results\" +  $("$user"+"_PasswordReset" + $date + ".log")) -Append
                                                                 $passLog | Out-File  ("$PWD\Results\" +  $("$user"+"_PasswordReset" + $date + ".log")) -Append

 Write-Host "A results file has been created: $PWD\Results\$("$user"+"_PasswordReset" + $date + ".log`n")" -ForegroundColor Yellow

Upvotes: 0

Views: 3004

Answers (2)

Ansgar Wiechers
Ansgar Wiechers

Reputation: 200193

If you enter a variable (e.g. $computername) at a Read-Host prompt you'll get a literal string '$computername'. To be able to expand that variable inside the string you need something like this:

$value = Read-Host
$ExecutionContext.InvokeCommand.ExpandString($value)

However, that will work only with regular strings, not with secure strings. And I wouldn't recommend it in the first place, because it would require the user to know which variables are available at script runtime. It also might lead to undesired side effects, because if you enter $computer_some%orOther Powershell will try to expand a (non-existing) variable $computer_some, since underscores are valid characters in identifiers. You'd have to specify the string as ${computer}_some%orOther. Also, what if you wanted a literal $ in the password? Your user would need to know that this character must be escaped.

If you must include the computername in the password (which, BTW, isn't a good idea in the first place, since including a known fact reduces the overall strength of the password) you should rather do it programmatically inside your code, like this:

$pw = Read-Host -Prompt 'Password'
$secpw = "$computername$pw" | ConvertTo-SecureString -AsPlainText -Force

or (if you must read the password as a secure string) like this:

$readpw = Read-Host -Prompt 'Password' -AsSecureString
$cred = New-Object Management.Automation.PSCredential ('x', $readpw)
$pw = $cred.GetNetworkCredential().Password
$secpw = "$computername$pw" | ConvertTo-SecureString -AsPlainText -Force

Upvotes: 3

Mathias R. Jessen
Mathias R. Jessen

Reputation: 174435

No, the Read-Host input is treated literally and variable references won't be expanded automatically.

You can force string expansion though:

PS> $ExecutionContext.InvokeCommand.ExpandString('$PWD')
C:\Users\Mathias

You could use Read-Host without -AsSecureString, wrap it in a call to the above function and then convert it to a SecureString with the ConvertTo-SecureString cmdlet:

PS> $computername = 'Computer123'
PS> $passwordSecure = ConvertTo-SecureString $ExecutionContext.InvokeCommand.ExpandString($(Read-Host "Input password")) -AsPlainText -Force
Input password: $($computername)!!
PS> [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($passwordSecure))
Computer123!!

Upvotes: 4

Related Questions