Middle name value still exists when script is run next time - powershell

I have a script to create a user account in my domain.
I have added a Middle name which is optional, but is required if the username is going to be a duplicate, so will take the middle name initial to differentiate.
The problem is that the following code keeps the last value. So if I specify first, middle and last name, then the next user is first and last name, then it still keeps the middle name.
Is there something that keeps the middle name empty when it runs the next time from scratch?
Do
{
#Getting variable for the First Name
Do
{
$firstname = Read-Host "Enter in the First Name"
Write-Host
if ($firstname -ge 1) {
Write-Host "First name is $firstname"
}
else
{
Write-Host "Please enter the first name" -ForegroundColor:Green
}
}
Until ($firstname -ge 1)
$option = Read-Host "Does the person have a middle name? (y/n)"
if ($option -eq "y")
{
#Getting variable for the Middle Name
Do
{
$middlename = Read-Host "Please enter the middle name"
Write-Host
if ($middlename -ge 1)
{
Write-Host "Middle name is $middlename"
}
else
{
Write-Host "Please enter the middle name" -ForegroundColor:Green
}
}
Until ($middlename -ge 1)
}
else
{
Write-Host "No middle name required"
}
#Getting variable for the Last Name
Do
{
$lastname = Read-Host "Enter in the Last Name"
Write-Host
if ($lastname -ge 1)
{
Write-Host "Last name is $lastname"
}
else
{
Write-Host "Please enter the last name" -ForegroundColor:Green
}
}
Until ($lastname -ge 1)
#Setting Full Name (Display Name) to the users first and last name
if ($middlename)
{
$fullname = "$firstname $middlename $lastname"
Write-Host $fullname
sleep 5
}
else
{
$fullname = "$firstname $lastname"
}
#Write-Host
#Setting username to first initial of first name along with the last name.
$i = 1
$logonname = $firstname.substring(0,$i) + $lastname

Just clear the variable at the beginning of your script.
Either:
$middlename = ""
Or use Remove-Variable
Remove-Variable middlename

This behavior comes from how you choose to launch a script. You have essentially two options in PowerShell, Invocation (Running) or dot-sourcing (Loading).
Take this short script.
#stack.ps1
"my variable should be empty $myVar"
$myvar = "cat"
"my variable should have a value $myVar"
When I run it / invoke it in PowerShell using the Invocation operator, look what happens:
PS> & .\stack.ps1
my variable should be empty
my variable should have a value cat
PS> & .\stack.ps1
my variable should be empty
my variable should have a value cat
PowerShell runs the script and so the script stores its variable values in the script scope. Meaning that when the script ends, the variables disappear.
Versus Dot-Sourcing
Now, compare that to dot-sourcing, which runs a script in your present context.
PS> . .\stack.ps1
my variable should be empty
my variable should have a value cat
PS> . .\stack.ps1
my variable should be empty cat #!!!! the value persisted!
my variable should have a value cat
Because it runs in your present context, the values persist when the script ends.
If I had to guess, I'd say you might be dot-sourcing your script instead of invoking it. It is a very easy thing to do and would explain what you're seeing.

Related

PowerShell "DO...Until" Try and Catch for valid data not working

I'm trying to use PowerShell to check if a user-provided position number exists. I want to loop this until the user provides a valid position. I am fairly new to PowerShell and I don't understand why it's not working...
#Start Store and Check Budget Position Number User Template#
Clear-Host
Do{
Try{
# Find the user template
$budgetpositionnumber = Read-Host "
What budget position number is the user filling?
"
Write-Host "
You entered budget position number: $budgetpositionnumber
"
# Find the position on Your.Domain
Get-ADuser $budgetpositionnumber
}
Catch{
Write-Host ("Failed to find position number " + $budgetpositionnumber) -ForegroundColor Red -ErrorAction Stop
}
} Until ($budgetpositionnumber -ne $Null)
#End Store and Check Budget Position Number Template#
Even if I enter invalid data, it still continues with the rest of the script. I want it to stop or loop until its' a vaild position number.
##Edited for clarity
"Even if I enter invalid data, it still continues with the rest of the script"
The do loop always ends because it's until condition always evaluates to $true no matter what the input is, this is because any string, even if Empty, is not equal to $null.
The condition:
Until ($budgetpositionnumber -ne $Null)
Should be testing if Get-ADuser found any object instead of testing if there was an input provided in Read-Host.
As for how you can approach the code:
do {
$userinput = Read-Host "something here..."
Write-Host "You entered budget position number: $userinput"
try {
$found = Get-ADuser $userinput
}
catch {
Write-Warning "No account with Name '$userinput' exists..."
}
} until ($found)

ArrayList not displaying when first referenced in function

Facing a couple logistical issues in PowerShell - clearly I'm missing a basic concept:
Setup: Create the menu.ps1 file (shown below), launch PowerShell 7.2.2 and call the file locally.
Issues:
The first time you choose option 1 for the ArrayList ($psArrayList), it does not display (although we see from the initial screen load that the items are populated). If you return to the menu and choose option 1 again, it will display on the second pass. ($psArray does load fine on first try, so is this is a type issue.?)
When the script ends, $psArrayList and $psArray are still in the current session variables, as indicated by: Get-Variable psArray*. Even if I instantiate them with $script:psArrayList = [System.Collections.ArrayList]#() and $script:psArray = #() they seem to stay within the session scope. Is there a "right" way to clear them when the ps1 ends?
menu.ps1 contents:
$psArrayList = [System.Collections.ArrayList]#()
# example of populating later in function etc...
$psArrayList.Add([pscustomobject]#{name="bird";color="blue"})
$psArrayList.Add([pscustomobject]#{name="cat";color="orange"})
$psArrayList.Add([pscustomobject]#{name="bear";color="brown"})
$psArray = #()
# example of populating later in function etc...
$psArray += "dog"
$psArray += "fish"
$psArray += "squirrel"
function End-Script {
Remove-Variable psArray*
Exit
}
function Display-Menu {
[int]$choice=-1
Write-Host "This is a menu..." -ForegroundColor Green
Write-Host "Here are your options:"
Write-Host
Write-Host "`t1 - ArrayList"
Write-Host "`t2 - Array"
Write-Host "`t0 - quit (do nothing)"
Write-Host
while ($choice -lt 0) { $choice= Read-Host -Prompt "Choose 1-2 (or 0 to quit)" }
Process-Menu($choice)
}
function Process-Menu([int]$choice) {
switch($choice) {
1 { Write-Host "You chose ArrayList:"; Write-Output $psArrayList }
2 { Write-Host "You chose Array:"; Write-Output $psArray }
0 { Write-Host "You chose to quit. Exiting."; End-Script }
}
$yn=""
while ($yn -eq "") { $yn= Read-Host -Prompt "Return to main menu? (y/n)" }
if ($yn -eq "y") { Display-Menu } else { Write-Host "Ending..."; End-Script }
}
Display-Menu
Regarding the first issue, you would need to use Out-Host or Out-Default so that both outputs (Write-Host together with the arrays) are correctly displayed to the console. See these helpful answers for in depth details on this:
https://stackoverflow.com/a/50416448/15339544
https://stackoverflow.com/a/34858911/15339544
Regarding the second issue, your End-Script function would have a scope issue, Remove-Variable is trying to remove variables defined inside the function's scope (Local), if you want to target the variables defined outside it (Script), you would need to use the -Scope parameter, for example:
function End-Script {
Get-Variable psArray* | Remove-Variable -Scope Script
# `Remove-Variable psArray* -Scope Script` would be valid too
}
From the cmdlet's Parameters section we can read the following for the -Scope parameter:
A number relative to the current scope (0 through the number of scopes, where 0 is the current scope and 1 is its parent)
In that sense, -Scope 1 would also work.
Below you can see an example of your script with some improvements as well as input validation:
$psArrayList = [System.Collections.ArrayList]#()
$psArrayList.AddRange(#(
[pscustomobject]#{name="bird";color="blue"}
[pscustomobject]#{name="cat";color="orange"}
[pscustomobject]#{name="bear";color="brown"}
))
$psArray = "dog", "fish", "squirrel"
function End-Script {
Get-Variable psArray* | Remove-Variable -Scope Script
}
function Display-Menu {
Write-Host "This is a menu..." -ForegroundColor Green
Write-Host "Here are your options:"
Write-Host
Write-Host "`t1 - ArrayList"
Write-Host "`t2 - Array"
Write-Host "`t0 - quit (do nothing)"
Write-Host
# one of many methods for input validation is a Recursive Script Block:
$tryInput = {
try {
[ValidateSet(0, 1, 2)] $choice = Read-Host "Choose 1-2 (or 0 to quit)"
$choice
}
catch {
Write-Warning 'Invalid choice!'
& $tryInput
}
}
Process-Menu (& $tryInput)
}
function Process-Menu([int] $choice) {
switch($choice) {
1 {
Write-Host "You chose ArrayList:"
$psArrayList | Out-Host
}
2 {
Write-Host "You chose Array:"
$psArray | Out-Host
}
0 {
Write-Host "You chose to quit. Exiting."
End-Script
Return # => Exit this function
}
}
$tryInput = {
try {
[ValidateSet('y', 'n')] $choice = Read-Host "Return to main menu? (y/n)"
$choice
}
catch {
Write-Warning 'Invalid choice!'
& $tryInput
}
}
# No need to check for `N`
if((& $tryInput) -eq 'y') { Display-Menu }
}
Display-Menu

Powershell Script to compare File-Hash from a Stream and published

Good morning guys,
I'm new to powershell scripting. And i can't figure out what I'm doing wrong.
I tried to write a .ps1 script to compare the hash value of a stream. I used the microsoft documentation for help and modify it to a runable script so i don't need to write it over and over again.
$wc = [System.Net.WebClient]::new()
$pkgurl = Read-Host "Please enter Package Url: "
$publishedHash = Read-Host "Enter Published Hash: "
$FileHash = Get-FileHash -InputStream ($wc.OpenRead($pkgurl))
if ($FileHash.Hash -eq $publishedHash) {
Write-Host "File Hash is equal to published Hash."
}
else {
Write-Host "File Hash NOT equal to published Hash."
}
When i run the script and enter the package url and the published Hash, the program all of a sudden abruptly shuts down.
Please, anyone an idea?
The script ends as it has nothing else to do.
You can add read-host at the end to wait for user input before it closes. (it wont do anything with the input, this just forces it to stay open until input has been made.)
Alternatively if you want to use it multiple times without it closing you can create a loop:
$KeepOpen = $true
While($KeepOpen -eq $true){
$wc = [System.Net.WebClient]::new()
$pkgurl = Read-Host "Please enter Package Url: "
$publishedHash = Read-Host "Enter Published Hash: "
$FileHash = Get-FileHash -InputStream ($wc.OpenRead($pkgurl))
if ($FileHash.Hash -eq $publishedHash) {
Write-Host "File Hash is equal to published Hash."
}
else {
Write-Host "File Hash NOT equal to published Hash."
}
$user_input = Read-Host "Please enter Y to run again"
if($user_input -ne "Y"){
$KeepOpen = $false
}
}
This will keep the script open so you can see the results and if you want it to run again insert Y and hit enter and you should be back to where you start.

Is there a Module or Something Similar for Interactive Prompts in PowerShell?

Is there something we can use in PowerShell to ask users to select one item from an array of items? For example, I like how Inquirer.js can do it.
I have also seen PoshGui, but it seems too much work to create just a simple prompt.
The reason we want something similar is that we need to provide deployment scripts for our clients and make deployment guides as easy as possible. Asking users to select one item on a screen is much better than asking them to insert some guid to a config file.
Do you have any suggestions for user prompts for arrays?
You can also try ps-menu module:
https://www.powershellgallery.com/packages/ps-menu
Sample:
I've used the Out-GridView cmdlet for this in the past. When used with the -PassThru switch it allows the selected item to be passed to a variable. The example image you've shown, when written using Out-GridView (ogv if you want to use the alias) is:
$task = Read-Host -Prompt "What do you want to do?"
if ($task -eq "Order a pizza") {
$pizza_sizes = #('Jumbo','Large','Standard','Medium','Small','Micro')
$size = $pizza_sizes | Out-GridView -Title "What size do you need?" -PassThru
Write-Host "You have selected $size"
}
There are many considerations to take into account with this, the windows might not appear where you want them to and they may appear behind others. Also, this is a very simple example that obviously needs error handling and other aspects built in. I'd suggest some testing or to get a second opinion from others on SO.
You can be as creative as you like of course..
Here's a small function that builds a console menu:
function Simple-Menu {
Param(
[Parameter(Position=0, Mandatory=$True)]
[string[]]$MenuItems,
[string] $Title
)
$header = $null
if (![string]::IsNullOrWhiteSpace($Title)) {
$len = [math]::Max(($MenuItems | Measure-Object -Maximum -Property Length).Maximum, $Title.Length)
$header = '{0}{1}{2}' -f $Title, [Environment]::NewLine, ('-' * $len)
}
# possible choices: didits 1 to 9, characters A to Z
$choices = (49..57) + (65..90) | ForEach-Object { [char]$_ }
$i = 0
$items = ($MenuItems | ForEach-Object { '[{0}] {1}' -f $choices[$i++], $_ }) -join [Environment]::NewLine
# display the menu and return the chosen option
while ($true) {
cls
if ($header) { Write-Host $header -ForegroundColor Yellow }
Write-Host $items
Write-Host
$answer = (Read-Host -Prompt 'Please make your choice').ToUpper()
$index = $choices.IndexOf($answer[0])
if ($index -ge 0 -and $index -lt $MenuItems.Count) {
return $MenuItems[$index]
}
else {
Write-Warning "Invalid choice.. Please try again."
Start-Sleep -Seconds 2
}
}
}
You can use it like below:
$menu = 'Pizza', 'Steak', 'French Fries', 'Quit'
$eatThis = Simple-Menu -MenuItems $menu -Title "What would you like to eat?"
switch ($eatThis) {
'Pizza' {
$menu = 'Jumbo', 'Large', 'Standard', 'Medium', 'Small', 'Micro'
$eatThat = Simple-Menu -MenuItems $menu -Title "What size do you need?"
Write-Host "`r`nEnjoy your $eatThat $eatThis!`r`n" -ForegroundColor Green
}
'Steak' {
$menu = 'Well-done', 'Medium', 'Rare', 'Bloody', 'Raw'
$eatThat = Simple-Menu -MenuItems $menu -Title "How would you like it cooked?"
Write-Host "`r`nEnjoy your $eatThat $eatThis!`r`n" -ForegroundColor Green
}
'French fries' {
$menu = 'Mayonaise', 'Ketchup', 'Satay Sauce', 'Piccalilly'
$eatThat = Simple-Menu -MenuItems $menu -Title "What would you like on top?"
Write-Host "`r`nEnjoy your $eatThis with $eatThat!`r`n" -ForegroundColor Green
}
}
Result:
All of the answers are correct, but I also wrote a few reusable PowerShell helper functions. Readme. I auto-generate basic looking WinForms. Looks ugly, but works.
https://github.com/Zerg00s/powershell-forms
$selectedItem = Get-FormArrayItem (Get-ChildItem)
$Delete = Get-FormBinaryAnswer "Delete file?"
$newFileName = Get-FormStringInput "Enter new file name" -defaultValue "My new file"
# -------------------------------------------------------------------------------
# Prepare the list of inputs that user needs to populate using an interactive form
# -------------------------------------------------------------------------------
$preDeployInputs = #{
suffix = ""
SPSiteUrl = "https://ENTER_SHAREPOINT_SITE.sharepoint.com"
TimeZone = "Central Standard Time"
sendGridRegistrationEmail = "ENTER_VALID_EMAIL_ADDRESS"
sendGridRegistrationPassword = $sendGridPassword
sendGridRegistrationCompany = "Contoso & Tailspin"
sendGridRegistrationWebsite = "https://www.company.com"
fromEmail = "no-reply#DOMAIN.COM"
}
$preDeployInputs = Get-FormItemProperties -item $preDeployInputs -dialogTitle "Fill these required fields"
Unfortunately, there's little that is built in, and that little is hard to discover - see below.
Potentially providing a dedicated Read-Choice cmdlet or enhancing Read-Host is being discussed in GitHub issue #6571.
The $host.ui.PromptForChoice() method supports presenting a menu of choices, but it has limitations:
The choices are presented on a single line (which may wrap).
Only single-character selectors are supported.
The selector character must be a part of the menu-item text.
Submitting a selection always requires pressing Enter
A ? option is invariably offered, even if you don't want to / need to provide explanatory text for each menu item.
Here's an example:
# The list of choices to present.
# Specfiying a selector char. explicitly is mandatory; preceded it by '&'.
# Automating that process while avoiding duplicates requires significantly
# more effort.
# If you wanted to include an explanation for each item, selectable with "?",
# you'd have to create each choice with something like:
# [System.Management.Automation.Host.ChoiceDescription]::new("&Jumbo", "16`" pie")
$choices = '&Jumbo', '&Large', '&Standard', '&Medium', 'Sma&ll', 'M&icro'
# Prompt the user, who must type a selector character and press ENTER.
# * Each choice label is preceded by its selector enclosed in [...]; e.g.,
# '&Jumbo' -> '[J] Jumbo'
# * The last argument - 0 here - specifies the default index.
# * The default choice selector is printed in *yellow*.
# * Use -1 to indicate that no default should be provided
# (preventing empty/blank input).
# * An invalid choice typed by the user causes the prompt to be
# redisplayed (without a warning or error message).
$index = $host.ui.PromptForChoice("Choose a Size", "Type an index and press ENTER:", $choices, 0)
"You chose: $($choices[$index] -replace '&')"
This yields something like:

Read-Host in While loop with if statement

This should be really simple, but I cannot make it work. I'm new to the PowerShell so my mistake is probably obvious. I'm trying to ask a user for an input using a while loop, and prompting the user to try again if what was entered was not numeric. In bash it would look like this:
while read -p "What is the first number? " first; do
if [[ $first = *[[:digit:]]* ]]; then
break # if $first is numeric, break loop
else
echo "$first is not numeric. Enter a numeric value. "
fi
done
Here's what I have for the PS:
While ($first = (Read-Host "What is the first number?") -as [double]) {
if ($first -eq $null) { Write-Host "Please enter a numeric value." }
else { break }
}
In the example above, I can break the loop fine when a number is entered, but I can't see the Write-Host statement if I type a string.
Looking at the While line:
While ($first = (Read-Host "What is the first number?") -as [double])
This will only enter/continue the loop body when the input is already a double, because it rolls up the cast to double as part of the loop condition. You want to enter the loop body on any input, and only check if it's a double afterwards. At least, that's what the bash code does:
While ($first = (Read-Host "What is the first number?")) {
if ( ($first -as [double]) -eq $null) { Write-Host "Please enter a numeric value." }
else { break }
}
Or you could continue using the cast as part of the condition by negating the whole expression, and thus avoid the need for the awkward break:
While (-not ($first = (Read-Host "What is the first number?") -as [double])) {
Write-Host "Please enter a numeric value."
}
You could use this to keep prompting for a valid number input as you are looking for.
Do {
"Please enter a numeric value." | Out-Host
$first = (Read-Host "What is the first number?") -as [double]
} While($null -eq $first)