Modify data in Powershell recursive function - powershell

I'm trying to create a powershell menu from some data inputs and I want an option to modify a parameter. I know that I'm doing wrong, but I don't find the correct way to do it. This is an example:
function callData($data){
write-host "folder selected: " $data
write-host "write NEWFOLDER to change folder"
$accion = Read-host "option"
switch ($accion)
{
'NEWFOLDER' {
$data = changeData
calldata $data
}
Default {}
}
}
function changeData(){
$newdata = read-host "New Folder"
return $newdata
}
$data = read-host "Folder"
while($true){
callData $data
}
Yes, I know, it's not beautiful and it doen't work correctly. With this code, it shows the new value the first time but in next loop it shows again the orignal value. My objective is to modify the original $data with the new value. I hope you understand me...

Related

How can I allow only Y/N to be input in Read-Host without having to press Enter in Powershell?

I'm trying to find a way to have something like a Read-Host to ask the user if they want to output to the file listed or not. With this I want them to either press y or n and then the code continues rather than then pressing y/n then pressing enter as well. At the moment this all works well but again it's not quite what I'm wanting.
I've tried looking into Readkey and SendKeys (to push Enter for the user) but neither work as they seem to only execute after the user has pushed Enter on the Read-Host. I'm still very new to Powershell so I'm not entirely sure whether it's actually possible or not and I've spent too much time googling/testing to find an answer that works. If I was to use Write-Host or something to do this, it needs to not show up in the log.
I've included the necessary part of my script below. It basically asks the user if the file location is correct. If it is they press y and it uses it for the output, otherwise if they push n then it loads the FolderBrowserDialog for them to select the folder they want.
I should also note this is all within a Tee-object as this code is what determines where the Tee-object output goes to.
$OutputYN = Read-Host "Do you want the output file generated to $startDirectory\FolderList.txt? (Y/N)"
If (“y”,”n” -notcontains $OutputYN) {
Do {
$OutputYN = Read-Host "Please input either a 'Y' for yes or a 'N' for no"
} While (“y”,”n” -notcontains $OutputYN)
}
if ($OutputYN -eq "Y") {
$OutputLoc = $startDirectory
}
elseif ($OutputYN -eq "N") {
$OutputLocDir = New-Object System.Windows.Forms.FolderBrowserDialog
$OutputLocDir.Description = "Select a folder for the output"
$OutputLocDir.SelectedPath = "$StartDirectory"
if ($OutputLocDir.ShowDialog() -eq "OK") {
$OutputLoc = $OutputLocDir.SelectedPath
$OutputLoc = $OutputLoc.TrimEnd('\')
}
}
EDIT:
I should have been a little more clear. I had already tried message box type stuff as well but I'd really prefer if there is a way that the user types in a y or a n. I'm not really interested in a popup box that the user has to click. If it's not possible then so be it.
Readkey is the right way.
Use the following as template.
:prompt while ($true) {
switch ([console]::ReadKey($true).Key) {
{ $_ -eq [System.ConsoleKey]::Y } { break prompt }
{ $_ -eq [System.ConsoleKey]::N } { return }
default { Write-Error "Only 'Y' or 'N' allowed!" }
}
}
write-host 'do it' -ForegroundColor Green
:prompt gives the outer loop (while) a name which can be used in the switch statement to directly break out entirely via break prompt (and not within the switch statement).
Alternative (for Windows):
Use a MessageBox.
Add-Type -AssemblyName PresentationFramework
$messageBoxResult = [System.Windows.MessageBox]::Show("Do you want the output file generated to $startDirectory\FolderList.txt?" , 'Question' , [System.Windows.MessageBoxButton]::YesNo , [System.Windows.MessageBoxImage]::Question)
switch ($messageBoxResult) {
{ $_ -eq [System.Windows.MessageBoxResult]::Yes } {
'do this'
break
}
{ $_ -eq [System.Windows.MessageBoxResult]::No } {
'do that'
break
}
default {
# stop
return # or EXIT
}
}
Not sure if this is possible in the console. But when I need the user to write one answer of a specified set, I use a do-until-loop like:
Do {
$a = Read-Host "Y / N"
} until ( 'y', 'n' - contains $a )
try this:
$title = 'Question'
$question = 'Do you want the output file generated to $startDirectory\FolderList.txt?'
$choices = New-Object Collections.ObjectModel.Collection[Management.Automation.Host.ChoiceDescription]
$choices.Add((New-Object Management.Automation.Host.ChoiceDescription -ArgumentList '&Yes'))
$choices.Add((New-Object Management.Automation.Host.ChoiceDescription -ArgumentList '&No'))
$decision = $Host.UI.PromptForChoice($title, $question, $choices, 1)
if ($decision -eq 0) {
Write-Host 'Yes'
} else {
Write-Host 'No'
}
If you are on Windows, you can do it :
[System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") | Out-Null
$result = [System.Windows.Forms.MessageBox]::Show('Do you want the output file generated to $startDirectory\FolderList.txt?' , "Question" , [System.Windows.Forms.MessageBoxButtons]::YesNo, [System.Windows.Forms.MessageBoxIcon]::Question)
if ($result -eq 'Yes') {
"Yes"
}
else
{
"No"
}

Powershell - OpenFileDialog.FileName and FileBrowserDialog.SelectedFolder return an object instead of a string

I am trying to build a Powershell script to convert some audio files in a folder, and I am using a FolderBrowserDialog to ask the user the location of the output folder, and an OpenFileDialog to get the path of the converter program (in case it is not in the same folder of the script).
Both dialogs are called withing separate functions, which are called by the main program.
The problem is that when I return 'OpenFileDialog.FileName' and 'FolderBrowserDialog.SelectedPath' from each function, I get an object which contains the path and some other values and not the path itself as a string.
This are the objects I get from the functions:
OpenFileDialog.FileName Result
FolderBrowserdialog.SelectedPath Result
The functions are:
Function GetConverterPath
{
$currentDirectory = Split-Path -Parent $PSCommandPath;
$isConverterInCurrentDirectory = Test-Path $($currentDirectory + "\tfa2wav.exe")
if($isConverterInCurrentDirectory)
{
return ($currentDirectory + "\tfa2wav.exe");
}
[System.Windows.MessageBox]::Show("The converter's *.exe file was not found in the same directory as this script`n" +
"Please, point to the right file in the next dialog...", 'Converter not found...','Ok','Information');
[System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms');
$OpenFileDialog = New-Object System.Windows.Forms.OpenFileDialog;
$OpenFileDialog.filter = 'Executable files (*.exe)|*.exe';
$result = $OpenFileDialog.ShowDialog()
if($result -eq 'OK')
{
return $OpenFileDialog.FileName;
}
else
{
exit;
}
}
Function AskForOutputFolder
{
[System.Windows.MessageBox]::Show("In the next dialog you should select the folder where the converted sound files will be placed",
'Information','Ok','Information');
[System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms")|Out-Null;
$folderBrowserDialog = New-Object System.Windows.Forms.FolderBrowserDialog;
$folderBrowserDialog.Description = "Select a folder";
$result = $folderBrowserDialog.ShowDialog()
if($result -eq 'OK')
{
return $folderBrowserDialog.SelectedPath;
}
else
{
exit;
}
}
Any ideas on how to solve this?
Also, how can I prevent the 'OK' messages from appearing in the console after each dialog?
From Get-Help 'about_return':
In PowerShell, the results of each statement are returned as
output, even without a statement that contains the Return keyword.
Use e.g [System.Void] struct as follows
[void][System.Windows.MessageBox]::Show(…);
[void][System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms');
As an alternative:
$null = [System.Windows.MessageBox]::Show(…);
Another alternative (slowest so avoid using this in loops):
[System.Windows.MessageBox]::Show(…) | Out-Null

Powershell dropdown select item, loads a specific text file into text box on form

In Powershell I want to have a dropdown list with following items:
Textfile1
Textfile2
Textfile3
When one if the list items is selected it reads file contents into textbox on form.
For example:
dropdown "Textfile1" is selected, that loads c:\Textfile1.txt into textbox on form.
already tried a function with if/elseif but having difficulty tying it together, unfortunately still learning Powershell.
I wouldn't recommend create a form with a dropdown list and return the choice from that, as Powershell is pretty much not built for that stuff.
Of course,I dont think it is impossible, but i'd use in this case the Out-GridView function to do work that is similar to your needs.
$files_location = "C:\yourlocation\*"
$options = Get-ChildItem $files_location
$user_choice = $options | Out-GridView -Title 'Select the File you want to show' -PassThru
Switch ($user_choice) {
#Condition to check:
{ $user_choice.Name -eq 'textfile1.txt' }
#Do something:
{
Write-Host "Im going to open $($user_choice.Name)"
#Open the file:
start "$user_choice"
}
#Continue your switch/case conditions here...
}
I'm using the output object from the Get-ChildItem function and outputting a gridview from that.
You can change the Switch case to if statement if you more comfortable with this function:
$files_location = "C:\yourlocation\*"
$options = Get-ChildItem $files_location
$user_choice = $options | Out-GridView -Title 'Select the File you want to show' -PassThru
if ($user_choice.Name -eq 'a.txt')
{
start $user_choice
}

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:

How do I update a variable in a variable in powershell?

I'm making a menu system for a script I have where I can change the values I need to by using the menu or by passing them as arguments to the script. One of the annoyances I have at the moment is after entering a new value when the menu refreshes, the variable in the menu text does not update to the new values.
$global:drive="C:"
$title = "Setup
"
$message = "The default variables are:
VARIABLES TO CHANGE
1. The Drive Location: $global:drive <<<--- This is the variable that does not update after I change it when I run the script.
"
$one = New-Object System.Management.Automation.Host.ChoiceDescription "&1 Drive", "The Drive Location:"
$options = [System.Management.Automation.Host.ChoiceDescription[]]($one)
:OuterLoop do
{
for ($i = 1; )
{
$result = $host.ui.PromptForChoice($title, $message, $options, 1)
switch ($result)
{
0 {$global:drive = Read-Host "Drive is $global:drive .Set the Drive Location";
"The Drive is now: $global:drive";
break;}
}
}
}
while ($y -ne 100)
Initially I did not set the variable to to global but read on here that that might help. It did not but it did not hurt either. I also tried setting it to script too. The variable does change, so this is cosmetic more than anything.
Thanks
I ran your code, but mine changes in the menu. The only thing I did was comment out your first $global:drive="C:". If this is always at the top of the script, then $global:drive will always display C:.
You can use the following code to check for the existance of a variable, then assign the value if it doesn't already exist:
if $(!(Get-Variable -Name Drive -Scope global -ErrorAction SilentlyContinue)) { $global:drive="C:" }
If the global variable Drive exists, it will do nothing. If it doesn't exist, $global:drive will be set to C:. Hope this helps.
Edit after #Norm comment:
The reason your message isn't updating, is because the $title is set outside of the loop. Because $Title is already defined, it doesn't need to change every time the loop runs. Simply move the declaration for $Title inside the loop before the line $result = $host.ui.PromptForChoice($title, $message, $options, 1). This should fix the problem you are having.
Edit2: I'm sorry, it's $message that needs moved, not $title