Here is my issue - I have a Powershell script that calls a bunch of information from an API, that comes in as JSON.
As an example (not the actual output, but good enough for my issue):
{
"fruit":[
{
"Type": "Apple",
"ID": 1
},
{
"Type": "Bannana",
"ID": 2
}
]
}
The API that is called has a search variable that is specified by the user, depending on what they specify, the API could return No results, a single result or multiple results.
What I want to do is to present the user with a list of Fruit, based off of the type field and an option: e.g.:
Press 1 for Apples
Press 2 for Bannanas
Press 0 to enter a new search field
If there are more options then obviously Press X for XXXX until all the options are accounted for.
I suspect I will have to do some form of loop through the JSON list to populate a set of fields, - I've never had an interactive section like this in a PS Script.
So - in the end, this was what I did to fix it - thanks to the commentors who pointed me in the right direction:
$variablearray = #()
$i = 1
foreach($_ in $fruitnames){
New-Variable -Name "$i" -Value $_
$variablearray += $i
$i = $i +1
}
$optionsarray = #()
$optionsarray += $Zero
foreach($_ in $variablearray){
$word = Convert-NumbertoWords $_
$tmp2 = (get-variable -name $_).value
$tmp = echo $word[1].trim()
$tmp3 = New-Object System.Management.Automation.Host.ChoiceDescription "$tmp", "$tmp2"
New-Variable -Name $tmp -Value $tmp3
$optionsarray += $tmp3
}
#Combine the options array to an actual options object
$options = [System.Management.Automation.Host.ChoiceDescription[]]($optionsarray)
#prompt the user for the choice
$title = 'Select the Fruit'
$message = 'Hover over each option to check what the description is'
$result = $host.ui.PromptForChoice($title, $message, $options, 0)
}
It loops through all the JSON Elements, creates a set of variables (this is needed for other parts of the script and for ease of use) and then creates an options array and then prompts the user for input.
Related
Ask user to enter a name, Search name in the names array person.dat file. If the name is found print a table, If the name is not found, print an error message and ask user for another name.
persons.dat.
George Nelson,56,78000.00
Mary Nathaniel,65,66300.00
Rosy Ferreira,32,39000.00
Guessing on this part.
While ($true){
Write-Host $("1. Search by user name")
Write-Host $("2. List all:)
$input = (Read-Host("Enter an option (0 to quit)"))##user will input value
#if 1 is entered (Read-Host("Enter user name"))
#if 2 is entered Print all#
#if 0 is entered quit.#
try{ ? }
catch {
## If input is invalid, restart loop
Write-host " User does not exist"
continue
}
0{
Write-Host $("Thank you. Bye!")
This bottom part will print all 3 in a table.
$data = Get-Content "persons.dat"
$line = $null;
[String[]] $name = #();
[int16[]] $age = #();
[float[]] $salary = #();
foreach ($line in $data)
{ #Split fields into values
$line = $line -split (",")
$name += $line[0];
$age += $line[1];
$salary += $line[2];
}
Write-Host $("{0,-20} {1,7} {2,11}" -f "Name", "Age", "Salary")
Write-Host $("{0,-20} {1,7} {2,11}" -f "-----------", "---", "-----------")
for
($nextItem=0 ; $nextItem -lt $name.length; $nextItem++)
{
$val1n = $name[$nextItem];
$val2n = $age[$nextItem]
$val3n = $salary[$nextItem]
Write-Host $("{0,-20} {1,7} {2,11:n2}" -f $val1n,
$val2n, $val3n)
}
Here is one way you could do it, hopefully the inline comments help you understand the logic. Since the persons.dat file you're showing us is comma-delimited, we can convert it to an object using ConvertFrom-Csv, by doing this, you won't have a need to construct the output to screen like you are doing with those Write-Host statements.
# Convert the file into an object
$persons = Get-Content persons.dat -Raw | ConvertFrom-Csv -Header "Name", "Age", "Salary"
function ShowMenu {
# simple function to clear screen and show menu when called
Clear-Host
'1. Search by user name'
'2. List all'
}
:outer while($true) {
# clear screen and show menu
ShowMenu
while($true) {
# ask user input
$choice = Read-Host 'Enter an option (0 to quit)'
# if input is 0, break the outer loop
if(0 -eq $choice) {
'Goodbye'
break outer
}
# if input is not 1 or 2
if($choice -notmatch '^(1|2)$') {
'Invalid input!'
$null = $host.UI.RawUI.ReadKey()
# restart the inner loop
continue
}
# if we are here user input was correct
break
}
$show = switch($choice) {
1 {
# if choice was 1, ask user for a user name
$user = Read-Host "Enter user name"
# if user name exists in the `$persons` object
if($match = $persons.where{ $_.Name -eq $user }) {
# output this to `$show`
$match
# and exit this switch
continue
}
# if user name was not found
"$user was not found in list."
}
2 {
# if input was 2, output `$persons` to `$show`
$persons
}
}
# show the object to the host
$show | Out-Host
# and wait for the user to press any key
$null = $host.UI.RawUI.ReadKey()
}
I have created 3 arrays, which store data from a separate file called household.dat, arr1 holds the ID, arr2 holds the income, and arr3 holds family members. The user is asked to enter an existent id (one that exists in the household file). If the id exists, I have to print the information related to that specific ID. The problem starts when I try to compare the user's input with the already stored id.
This is what I have so far.
$myarr contains the first set of numbers in the first column which is the ID
$myarr1 contains the second set of numbers in the second column, which is the income
$myarr2 contains the third set of numbers in the third column which is the members
10041,12180.00,4
15298,89254.00,3
10562,13240.00,3
13256,19800.00,2
47742,67189.00,4
14830,22458.00,8
19000,17000.00,2
21132,18125.00,7
23541,15623.00,2
82772,56878.00,2
32100,3200.00,6
67733,98113.00,5
36002,6500.00,5
37734,45144.00,4
65410,11970.00,2
47352,8900.00,3
62159,10000.00,2
92803,6200.00,1
"{0,25:n3}" -f "Household Statistics"
write-output "--------------------------------"
write-output "1. Search by Household ID"
write-output "2. List all"
$option = read-host "Enter a option (0 to quit)"
$myarr = #()
$myarr1 = #()
$myarr2 = #()
$id = #()
if($option -eq 1)
{
$id = Read-Host -Prompt "Enter Household ID "
foreach ($line in $file)
{
$line = $line -split (",")
$myarr = $line[0]
$myarr1 = $line[1]
$myarr2 = $line[2]
if ($myarr -contains $id)
{
write-output "--------------------------------"
write-host "Statistics for household" $id
write-output "--------------------------------"
"{0,-11} {1,10} {2,15}" -f "Household ID", "Income",
"Members"
}
elseif ($myarr -notcontains $id)
{
""
write-output "Sorry, entered Household ID not found"
break;
""
}
}
If you're not reading from a file as CSV, but somehow created 3 (same-length) arrays, you can start your code by combining the array values into workable objects like this:
$data = for ($i = 0; $i -lt $myarr.Count; $i++) {
[PsCustomObject]#{
ID = $myarr[$i]
Income = $myarr1[$i]
Members = $myarr2[$i]
}
}
However, reading your question a couple of times over, and looking at the examples you give for the 3 arrays, it seems to me that your file household.dat is in fact a CSV file without headers.
Instead of reading that file with Get-Content and then loop over the lines, manually splitting them on the comma as in your code, you should use Import-Csv, so you will obtain an array of objects where each object has both the 'ID', the 'Income' AND the corresponding 'Members' properties, nicely packed together.
In that case just do:
$data = Import-Csv -Path 'X:\somewhere\household.dat' -Header ID, Income, Members
Then your code would be much simpler like:
# loop over the records in the $data
$id = Read-Host -Prompt "Enter Household ID"
# see if you can find a matching record
$record = $data | Where-Object { $_.ID -eq $id }
if ($record) {
Write-Host "--------------------------------"
Write-Host "Statistics for household $id"
Write-Host "--------------------------------"
"{0,-11} {1,10} {2,15}" -f $record.ID, $record.Income, $record.Members
}
else {
""
Write-Host "Sorry, entered Household ID '$id' not found"
""
}
This is how I modify my Powershell array:
ForEach ($userID in $usersList) {
$allUsers += [pscustomobject]#{ID=$usersCounterTable;UserID=$userID;Name=$userInfo.DisplayName;Ext=$userInfo.ipPhone;Cellphone=$userInfo.mobile;Enabled=$isEnabled;VDI=$computerType;Title=$userTitle;}
$usersCounter += 1
$usersCounterTable = "[$usersCounter]"
}
Later in the code, the table is displayed and I want the user to be able to type a number to open the value, the number actually being the array index/offset (minus 1). I cannot find out how to do this.
$userID is actually the user's selection, because they can also type another employee's code to search, or search for his name for instance. I want the user to be able to select the array index number.
if (($userID.length -gt 0) -and ($userID.length -lt 5)) {
$indexNumber = ($userID - 1)
[????] $userFinalChoice = $allUsers[$userID].Name # NOT VALID
}
Above code works, if the user enter a number between 1 and 9999...
And then I would like to do this: $allUsers[$userID] ($userID being the number the user selected with Read-Host). Only, $allUsers[$userID].Name is not valid, but $allUsers[1].Name is. If I can figure this out, I'll be able to fix the rest of it and search for the return value.
Also need to make sure user doesn't input an index that is out of bounds of $usersList (Using $ErrorActionPreference = "SilentlyContinue" would probably work as it just reject the search reject but it's not that clean.)
From what I understand, I'm actually looking for the reverse of $usersList.IndexOf(‘David’), I want to provide the index and get returned the name.
Much appreciated - Powershell beginner.
The first code block you show us is really confusing, since you seem to grab user details from just... somewhere, so there is no telling if this info indeed belongs to the same user or not.
Also, I don't really think it is a good idea to use a formatted table as selection menu, especialy if the list gets large. Maybe you should think of building a form with a listbox or use the Out-GridView for this as Lee_Dailey suggested.
Anyway, if you want it as console menu, first make sure the ID number (really the index to select) starts with 1
$usersCounter = 1
# collect an array of PsCustomObjects in variable $allUsers
$allUsers = foreach ($userID in $usersList) {
# don't use $allUsers += , simply output the object
[PsCustomObject]#{
ID = "[$usersCounter]"
UserID = $userID
Name = $userInfo.DisplayName
Ext = $userInfo.ipPhone
Cellphone = $userInfo.mobile
Enabled = $isEnabled
VDI = $computerType
Title = $userTitle
}
$usersCounter++ # increment the counter
}
Next, show this as table so folks can select one of the users by typing the number displayed in the 'ID' column.
Do this in a loop, so when someone types anything else than a valid number, the menu is displayed again.
# start an endless loop
while ($true) {
Clear-Host
$allUsers | Format-Table -AutoSize
$userID = Read-Host "Enter the [ID] number to select a user. Type 0 or Q to quit"
if ($userID -eq '0' -or $userID -eq 'Q') { break } # exit from the loop, user quits
# test if the input is numeric and is in range
$badInput = $true
if ($userID -notmatch '\D') { # if the input does not contain an non-digit
$index = [int]$userID - 1
if ($index -ge 0 -and $index -lt $allUsers.Count) {
$badInput = $false
# everything OK, you now have the index to do something with the selected user
# for demo, just write confirmation to the console and exit the loop
Clear-Host
Write-Host "You have selected $($allUsers[$index].Name)" -ForegroundColor Green
break
}
}
# if you received bad input, show a message, wait a couple
# of seconds so the message can be read and start over
if ($badInput) {
Write-Host "Bad input received. Please type only a valid number from the [ID] column." -ForegroundColor Red
Start-Sleep -Seconds 4
}
}
I am wanting to pull the value of an array index element into another seperate value so that I can display the value to verify that was the correct choice from the user.
My first test was to just try and put the $Array[$ArrayIndex] into the verification read-host statement, and it returned the type of the object that I referenced in the array index, but when I just put the $Array[$ArrayIndex] into the powershell command line, it returns the info I want. I also tried to pull that into another variable like this: $arraychoice = $array[$arrayindex] and it pulls the same type statement:
Microsoft.PowerShell.Commands.Internal.Format.FormatEntryData
This is the code that I am currently using to initialize the array, enumerate it, and then ask the user if they need to change the time zone, allow them to choose a new TZ, and then verify their choice.
#get list of timezones.$tZs = Get-TimeZone -ListAvailable
#setup array for time zones
$tZNames = $tZs.id
$tzArray = #()
[string[]]$tZArray.AddRange($tZNames)
$tzAvail = $tzarray | foreach -Begin {$i=0} -Process {
$i++
"{0:D2}. {1}" -f $i,$_
}
$tzlist = $tzavail
#Ask if they need to change the current time zone
while ($null -eq $tzchange)
{
while( -not (($tzchange = (Read-host "Do you need to update the system to a different timezone?")) -match "y|n")){ "Y or N ?"}
if ("y" -eq $tzchange)
{
write-host "List of available Timezones:"
$tzlist
while ("y" -ne $tzverif)
{
[int]$tzNew = Read-host "What timezone would you like to update the time zone to? (# from previous list)"
$tzActual = $tzList[$tzNew]
while( -not (($tzverif= (Read-host "You entered $tzActual is that correct?")) -match "y|n")){ "Y or N ?"}
}
}
}
I am wanting to just get the variable I pull the choice into to display the index element value:
in my tests I chose central standard time, so the choice of 15, should yield the value of:
15. Central Standard Time
In the line where $tzactual is
while( -not (($tzverif= (Read-host "You entered $tzActual is that correct?")) -match "y|n")){ "Y or N ?"}
the problem was your use of Format-Wide. that cmdlet - and all the Format-* cmdlets - chop up your objects, wrap them in formatting code, and then send those butchered bits out. [grin] they should ONLY be used for final output to screen OR final output to a plain text file.
NEVER use them for anything where you need to use the objects later ... you won't have those objects any more.
here is an alternate way to get the user to choose an item from a list of the objects in a collection ...
$TimeZoneList = (Get-TimeZone -ListAvailable).DisplayName
$CurrentTimeZone = (Get-TimeZone).DisplayName
'The current time zone is {0}.' -f $CurrentTimeZone
$Choice = Read-Host 'Do you want to change that? [n/y]'
if ($Choice -eq 'y')
{
$Choice = $Null
while ([string]::IsNullOrEmpty($Choice))
{
$TimeZoneList.ForEach({
'{0, 4} - {1}' -f $TimeZoneList.IndexOf($_), $_
})
''
$Choice = Read-Host 'Please select a TimeZone by number '
if ([int]$Choice -notin 0..$TimeZoneList.GetUpperBound(0))
{
[System.Console]::Beep(1000, 300)
Write-Warning ''
Write-Warning (' Your coice [ {0} ] is not valid.' -f $Choice)
Write-Warning ' Please try again ...'
Write-Warning ''
$Choice = $Null
pause
}
else
{
$ChosenTimeZone = $TimeZoneList[[int]$Choice]
'You chose {0} - {1}.' -f $Choice, $ChosenTimeZone
$Choice = Read-Host 'Is that the TimeZone you want to use? [n/y]'
if ($Choice -eq 'n')
{
$Choice = $ChosenTimeZone = $Null
}
}
} # end >>> while ([string]::IsNullOrEmpty($Choice))
} # end >>> if ($Choice -eq 'y')
''
'Current TimeZone = {0}' -f $CurrentTimeZone
'Chosen replacement TimeZone = {0}' -f $ChosenTimeZone
''
invalid choice output ...
Please select a TimeZone by number : 1234
WARNING:
WARNING: Your coice [ 1234 ] is not valid.
WARNING: Please try again ...
WARNING:
Press Enter to continue...:
valid choice output ...
Please select a TimeZone by number : 12
You chose 12 - (UTC-07:00) Mountain Time (US & Canada).
Is that the TimeZone you want to use? [n/y]: y
Current TimeZone = (UTC-06:00) Central Time (US & Canada)
Chosen replacement TimeZone = (UTC-07:00) Mountain Time (US & Canada)
In this snippet I have a function (FDiskScan) that gets a computer name as an input and should return an array of objects.
function FDiskScan ([String] $name)
{
$outarray = [System.Collections.ArrayList]#()
$diskscan = Get-WmiObject Win32_logicaldisk -ComputerName $name
foreach ($diskobj in $diskscan)
{
if($diskobj.VolumeName -ne $null )
{
$max = $diskobj.Size/1024/1024/1024
$free = $diskobj.FreeSpace/1024/1024/1024
$full = $max - $free
$obj = #{
'ID' = $diskobj.deviceid
'Name' = $diskobj.VolumeName
'TotalSpace' = $max
'FreeSpace' = $free
'OccupiedSpace' = $full }
$TMP = New-Object psobject -Property $obj
$outarray.Add($TMP)
}
}
return $outarray
}
$pc = "INSERT PC NAME HERE"
$diskdata = [System.Collections.ArrayList]#()
$diskdata = FDiskScan($pc)
foreach ($disk in $diskdata)
{
Write-Host "Disco: " $disk.ID
Write-Host "Espaço Total: " ([math]::Round($disk.TotalSpace, 2)) "GB"
Write-Host "Espaço Ocupado: " ([math]::Round($disk.OccupiedSpace, 2)) "GB"
Write-Host "Espaço Livre" ([math]::Round($disk.FreeSpace, 2)) "GB" "`n"
}
Within the function with debugging and going into the variables I can see that everything is alright, and when the array gets out of the function and into the script scope it adds 2 more entries.
While in debug mode it tells me that $outarry within FDiskScan has the two disks that I have on my system organised as they should be.
However on:
$diskdata = FDiskScan($pc)
It says that it has an entry of value 0 on index 0 and of value 1 on index 1, then the disks follow suit, first disk C: in index 3 and disk D in index 4.
The expected behaviour was for index 0 and 1 having disks C and D respectively not a phantom 0 and 1 entries.
When adding an object to an array list in PowerShell (i.e. $outarray.Add($TMP)) the index, the object was added at, gets returned. As you don't assign the return value to a variable the function returns a System.Array containing the indexes and the entries of the array list returned by return $outarray. That's the reason why your functions return value contains 4 elements. Furthermore your functions return value in this case is not of type System.Collections.ArrayList but of type System.Array.
To avoid that behaviour do the following.
$null = $outarray.Add($TMP);
You are seeing 0, 1 because of this line - $outarray.Add($TMP). Change it to $outarray.Add($TMP) | Out-Null. I think PowerShell is printing the index when adding to the array.