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
}
}
Related
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.
I'm attempting to compare two arrays: one contains a list of usernames (dynamic, sometimes more usernames, sometimes less) and the other contains a list of file names (also dynamic). Every file name contains the username along with other text, e.g "Username report [date].xlsx". The goal is to match the elements between Array A and Array B.
Array A is just usernames.
Output of Array A, contained in $Username is just:
PersonA
PersonB
PersonC
etc...
Array B contains filepaths, but I can narrow it down to just filenames like so $ArrayB.Name (the full path would be $ArrayB.FullName). The naming format for Array B is "Username report [date].xlsx".
Output of Array B, contained within $LatestFiles.Name (for the file name) is:
PersonA Report 1-1-21.xlsx
PersonB Report 1-1-21.xlsx
PersonC Report 1-1-21.xlsx
After matching, the final piece would be if element in Array A matches element in Array B, attach ArrayB.FullName to the corresponding username + "#domain.com".
Unfortunately I can't even get the matching to work properly.
I've tried:
foreach ($elem in $UserName) { if ($LatestFiles.Name -contains $elem) { "there is a match" } }
and
foreach ($elem in $UserName) {
if($LatestFiles.Name -contains $elem) {
"There is a match"
} else {
"There is no match"
}
}
and a couple different variations, but I can't get them to output the matches. Any assistance is appreciated.
Short answer to why you can't get matches:
-Contains is meant for matching a value against a collection, not a String. You would be better off using -Like for your comparison operator. Or, at the very least, you may be trying to see if the name of the file, and not simply the part of the name that holds the user, is in the collection of user names.
I sounds like you are not simply comparing the arrays, but the more complicated matter of what you do with the two matching elements.
$LatestFiles |
ForEach-Object {
# first, let's take the formatted file name and make it more usable
$fileName = $_.BaseName -split ' ' # BaseName over Name so that it strips the extension
Write-Output #{
User = $fileName[0]
Date = $fileName[2]
File = $_
}
} -PipelineVariable FileData |
# next, only accept valid users
Where-Object User -In $UserName |
# note that we don't need the value from $UserName because we already have it in $FileData.User (what we matched)
ForEach-Object {
# finally do your thing with it.
$userWithDomain = "$($FileData.User)#domain.com"
Write-Output $userWithDomain # or whatever you want
}
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)
I am trying to figure out how to do the following.
Read a text file with phone numbers in a list that are available each number on a separate line.
Output the available list to the script user to be able to select the best one.
Input the $number into another command to add it to the new user.
I have the following so far
$numbers = gc 'C:\temp\file.txt'
I can output the data by using $numbers[1], $numbers[2], etc., but how can I output this to look good and allow the user to select the best number with all options in the text file? I can do it manually obviously, but if I do 3 x $numbers[1], $numbers[2], $numbers[3] it will miss the numbers 4, 5, 6 etc.
try Something like this :
$selected=Get-Content "c:\temp\phone.txt" | Out-Gridview -Title "Select your choice" -OutputMode Single
if ($selected)
{
Write-Host "your choice is $selected"
}
Something like this:
$numbers = Get-Content 'C:\temp\file.txt'
$i = 1
foreach($n in $numbers){
Write-host "Number $i - $n"
$i++
}
$n = Read-host "Choose a number"
$chosenNumber = $number[$n-1]
I'm having an issue trying to find the index number of an item in an array that contains Active directory users.
I create the array as follows:
$outarray = #()
$outarray = get-aduser -Filter * -Properties LastLogon | select "Name","SAMAccountName","LastLogon" | sort samaccountname
Now i have the users in an array, and i can prove it using standard variable queries
$outarray[0]
$outarray[1]
Returns exactly what i expect.
BUT
I completely fail to search for the index of a name or SAMAccountName in the array, as they are properties of the array.
$index = [array]::IndexOf($outarray.samaccountname, "testuser")
returns -1 (not found) or 0 only if testuser is the FIRST user in the array.
I cannot find any other user index in the array.
My goal after getting the index is to use it to update the property for lastlogon. This works if i do it manually
e.g.
$outarray[123].lastlogon = 12345678
The only way i can make this work is to manually build the array initially, one entry at a time instead of filling directly
foreach ($user in $outArray)
{
$myobj = #()
$myobj = "" | Select "Name","SAMAccountName","LastLogon"
#fill the object
$myobj.Name = $user.name
$myobj.SAMAccountName = $user.samaccountname
$myobj.LastLogon = $user.LastLogon
#Add the object to the array
$userarray += $myobj
}
$userarray[[array]::IndexOf($userarray.samaccountname, "testuser")].LastLogon = 12345678
Then the search works. I assume this has to do with property types, but im completely out my depth by this stage.
Thanks in advance for any help, I'm no expert on powershell arrays, they confuse me! :)
I think you're looking at this the wrong way. Instead of finding the index of a specific item and then access that item by its index you could do it the PoSh way by filtering the array for the item you want to update, like this:
$userarray | ? {
$_.SamAccountName -eq 'testuser'
} | % {
$_.LastLogon = 12345678
}
or like this:
$acct = $userarray | ? { $_.SamAccountName -eq 'testuser' } | select -First 1
$acct.LastLogon = 12345678