Modify the powershell script to add additional parameter - powershell
the below script was created by our pervious IT guy, what this script does is basically replaces the Funduc Software (RSP and RSBE) config file in the logged in user's Appdata so we can modify what the software will search over. E.g. Excel Word etc.
I need to modify this script in such a way that it when ran without any parameter provided it runs as it but if we specify a parameter it automatically runs the config we need.
Right now if we run .\RSUpdateConfigFile.ps1 it starts the script and waits for user to enter a number which corresponds to the extensions we need to search over, this works fine as is.
What we'd like it to do is if we run .\RSUpdateConfigFile.ps1 -L_V_1_String_PreSelection 10 it should execute the script and without prompting the user automatically update the config file with Word extensions.
Below is the code we are using. Any help or advice would be appreciated.
Param(
$L_V_1_String_DisplayComputerName = $env:ComputerName,
$L_V_1_String_DestinationPath = $env:APPDATA,
$L_V_Array_String_ConfigurationFilesToCopy = #(
"A:\Input\Replace Studio.cfg",
"A:\Input\Replace Studio Business Edition.cfg"
),
$L_V_HashTable_String_FileExtensions = [Ordered] #{
"All files" = "*.*";
"All Excel, PowerPoint, Visio, and Word files" = "*.xlm;*.xls;*.xlt;*.xlsm;*.xltm;*.xlsx;*.xltx;*.ppt;*.pps;*.pot;*.pptm;*.ppsm;*.potm;*.pptx;*.ppsx;*.potx;*.vsl;*.vsd;*.vsdx;*.vsdm;*.vssm;*.vstm;*.vssx;*.vstx;*.vss;*.vtt;*.vsw;*.vdx;*.vsx;*.vtx;*.doc;*.dot;*.docm;*.dotm;*.docx;*.dotx";
"All Excel, PowerPoint, Visio, and Word internal files" = "*_A_I_*.xlm;*_A_I_*.xls;*_A_I_*.xlt;*_A_I_*.xlsm;*_A_I_*.xltm;*_A_I_*.xlsx;*_A_I_*.xltx;*_A_I_*.ppt;*_A_I_*.pps;*_A_I_*.pot;*_A_I_*.pptm;*_A_I_*.ppsm;*_A_I_*.potm;*_A_I_*.pptx;*_A_I_*.ppsx;*_A_I_*.potx;*_A_I_*.vsl;*_A_I_*.vsd;*_A_I_*.vsdx;*_A_I_*.vsdm;*_A_I_*.vssm;*_A_I_*.vstm;*_A_I_*.vssx;*_A_I_*.vstx;*_A_I_*.vss;*_A_I_*.vtt;*_A_I_*.vsw;*_A_I_*.vdx;*_A_I_*.vsx;*_A_I_*.vtx;*_A_I_*.doc;*_A_I_*.dot;*_A_I_*.docm;*_A_I_*.dotm;*_A_I_*.docx;*_A_I_*.dotx";
"All Excel files" = "*.xlm;*.xls;*.xlt;*.xlsm;*.xltm;*.xlsx;*.xltx";
"All Excel internal files" = "*_A_I_*.xlm;*_A_I_*.xls;*_A_I_*.xlt;*_A_I_*.xlsm;*_A_I_*.xltm;*_A_I_*.xlsx;*_A_I_*.xltx";
"All PowerPoint files" = "*.ppt;*.pps;*.pot;*.pptm;*.ppsm;*.potm;*.pptx;*.ppsx;*.potx";
"All PowerPoint internal files" = "*_A_I_*.ppt;*_A_I_*.pps;*_A_I_*.pot;*_A_I_*.pptm;*_A_I_*.ppsm;*_A_I_*.potm;*_A_I_*.pptx;*_A_I_*.ppsx;*_A_I_*.potx";
"All Visio files" = "*.vsl;*.vsd;*.vsdx;*.vsdm;*.vssm;*.vstm;*.vssx;*.vstx;*.vss;*.vtt;*.vsw;*.vdx;*.vsx;*.vtx";
"All Visio internal files" = "*_A_I_*.vsl;*_A_I_*.vsd;*_A_I_*.vsdx;*_A_I_*.vsdm;*_A_I_*.vssm;*_A_I_*.vstm;*_A_I_*.vssx;*_A_I_*.vstx;*_A_I_*.vss;*_A_I_*.vtt;*_A_I_*.vsw;*_A_I_*.vdx;*_A_I_*.vsx;*_A_I_*.vtx";
"All Word files" = "*.doc;*.dot;*.docm;*.dotm;*.docx;*.dotx";
"All Word internal files" = "*_A_I_*.doc;*_A_I_*.dot;*_A_I_*.docm;*_A_I_*.dotm;*_A_I_*.docx;*_A_I_*.dotx";
"All Word agreement files" = "*_A_*.doc;*_A_*.dot;*_A_*.docm;*_A_*.dotm;*_A_*.docx;*_A_*.dotx"
"All Word form files" = "*_F_*.doc;*_F_*.dot;*_F_*.docm;*_F_*.dotm;*_F_*.docx;*_F_*.dotx"
"All Word letter files" = "*_L_*.doc;*_L_*.dot;*_L_*.docm;*_L_*.dotm;*_L_*.docx;*_L_*.dotx"
},
$L_V_1_String_Placeholder = "%Placeholder%"
)
Clear-Host
$L_V_1_Integer_Index = 0
$L_V_1_String_Prompt = "Enter the number representing your desired option:`n`n"
foreach ($L_V_1_String_Key in ($L_V_HashTable_String_FileExtensions.Keys))
{
$L_V_1_String_Prompt += "$L_V_1_Integer_Index) "+$L_V_1_String_Key+"`n`n"
$L_V_1_Integer_Index += 1
}
Try
{
$L_V_1_String_UserSelection = Read-Host -Prompt $L_V_1_String_Prompt
if (($L_V_1_String_UserSelection -eq $null) -or (-not($L_V_1_String_UserSelection -match "\d")))
{
Throw
}
else
{
Try
{
#Replace placeholders, and save files to destination.
foreach($L_V_1_String_ConfigurationFileToCopy in $L_V_Array_String_ConfigurationFilesToCopy)
{
$L_V_1_FileInfo_ConfigurationFileToCopy = [io.FileInfo] $L_V_1_String_ConfigurationFileToCopy
$L_V_1_DestinationFilePath = "$L_V_1_String_DestinationPath\"+$L_V_1_FileInfo_ConfigurationFileToCopy.Name
Get-Content $L_V_1_FileInfo_ConfigurationFileToCopy -ErrorAction Stop | Foreach-Object {$_.replace($L_V_1_String_Placeholder , $L_V_HashTable_String_FileExtensions[[int]$L_V_1_String_UserSelection])} | Set-Content -Path $L_V_1_DestinationFilePath -ErrorAction Stop
Write-Host "File '$L_V_1_DestinationFilePath' has been copied!"
}
}
Catch
{
Write-Host $_.Exception.Message
Exit 1
}
}
}
Catch
{
Write-Host "That is not a valid choice!"
Exit 1
}
Write-Host "ComputerName: $L_V_1_String_DisplayComputerName"
Pause
i added your parameter to the script, it should only prompt you when not provided:
Param(
$L_V_1_String_DisplayComputerName = $env:ComputerName,
$L_V_1_String_DestinationPath = $env:APPDATA,
$L_V_Array_String_ConfigurationFilesToCopy = #(
"A:\Input\Replace Studio.cfg",
"A:\Input\Replace Studio Business Edition.cfg"
),
$L_V_HashTable_String_FileExtensions = [Ordered] #{
"All files" = "*.*";
"All Excel, PowerPoint, Visio, and Word files" = "*.xlm;*.xls;*.xlt;*.xlsm;*.xltm;*.xlsx;*.xltx;*.ppt;*.pps;*.pot;*.pptm;*.ppsm;*.potm;*.pptx;*.ppsx;*.potx;*.vsl;*.vsd;*.vsdx;*.vsdm;*.vssm;*.vstm;*.vssx;*.vstx;*.vss;*.vtt;*.vsw;*.vdx;*.vsx;*.vtx;*.doc;*.dot;*.docm;*.dotm;*.docx;*.dotx";
"All Excel, PowerPoint, Visio, and Word internal files" = "*_A_I_*.xlm;*_A_I_*.xls;*_A_I_*.xlt;*_A_I_*.xlsm;*_A_I_*.xltm;*_A_I_*.xlsx;*_A_I_*.xltx;*_A_I_*.ppt;*_A_I_*.pps;*_A_I_*.pot;*_A_I_*.pptm;*_A_I_*.ppsm;*_A_I_*.potm;*_A_I_*.pptx;*_A_I_*.ppsx;*_A_I_*.potx;*_A_I_*.vsl;*_A_I_*.vsd;*_A_I_*.vsdx;*_A_I_*.vsdm;*_A_I_*.vssm;*_A_I_*.vstm;*_A_I_*.vssx;*_A_I_*.vstx;*_A_I_*.vss;*_A_I_*.vtt;*_A_I_*.vsw;*_A_I_*.vdx;*_A_I_*.vsx;*_A_I_*.vtx;*_A_I_*.doc;*_A_I_*.dot;*_A_I_*.docm;*_A_I_*.dotm;*_A_I_*.docx;*_A_I_*.dotx";
"All Excel files" = "*.xlm;*.xls;*.xlt;*.xlsm;*.xltm;*.xlsx;*.xltx";
"All Excel internal files" = "*_A_I_*.xlm;*_A_I_*.xls;*_A_I_*.xlt;*_A_I_*.xlsm;*_A_I_*.xltm;*_A_I_*.xlsx;*_A_I_*.xltx";
"All PowerPoint files" = "*.ppt;*.pps;*.pot;*.pptm;*.ppsm;*.potm;*.pptx;*.ppsx;*.potx";
"All PowerPoint internal files" = "*_A_I_*.ppt;*_A_I_*.pps;*_A_I_*.pot;*_A_I_*.pptm;*_A_I_*.ppsm;*_A_I_*.potm;*_A_I_*.pptx;*_A_I_*.ppsx;*_A_I_*.potx";
"All Visio files" = "*.vsl;*.vsd;*.vsdx;*.vsdm;*.vssm;*.vstm;*.vssx;*.vstx;*.vss;*.vtt;*.vsw;*.vdx;*.vsx;*.vtx";
"All Visio internal files" = "*_A_I_*.vsl;*_A_I_*.vsd;*_A_I_*.vsdx;*_A_I_*.vsdm;*_A_I_*.vssm;*_A_I_*.vstm;*_A_I_*.vssx;*_A_I_*.vstx;*_A_I_*.vss;*_A_I_*.vtt;*_A_I_*.vsw;*_A_I_*.vdx;*_A_I_*.vsx;*_A_I_*.vtx";
"All Word files" = "*.doc;*.dot;*.docm;*.dotm;*.docx;*.dotx";
"All Word internal files" = "*_A_I_*.doc;*_A_I_*.dot;*_A_I_*.docm;*_A_I_*.dotm;*_A_I_*.docx;*_A_I_*.dotx";
"All Word agreement files" = "*_A_*.doc;*_A_*.dot;*_A_*.docm;*_A_*.dotm;*_A_*.docx;*_A_*.dotx"
"All Word form files" = "*_F_*.doc;*_F_*.dot;*_F_*.docm;*_F_*.dotm;*_F_*.docx;*_F_*.dotx"
"All Word letter files" = "*_L_*.doc;*_L_*.dot;*_L_*.docm;*_L_*.dotm;*_L_*.docx;*_L_*.dotx"
},
$L_V_1_String_Placeholder = "%Placeholder%",
$L_V_1_String_PreSelection
)
Clear-Host
$L_V_1_Integer_Index = 0
$L_V_1_String_Prompt = "Enter the number representing your desired option:`n`n"
foreach ($L_V_1_String_Key in ($L_V_HashTable_String_FileExtensions.Keys))
{
$L_V_1_String_Prompt += "$L_V_1_Integer_Index) "+$L_V_1_String_Key+"`n`n"
$L_V_1_Integer_Index += 1
}
Try
{
if ($L_V_1_String_PreSelection){
$L_V_1_String_UserSelection = $L_V_1_String_PreSelection
} else {
$L_V_1_String_UserSelection = Read-Host -Prompt $L_V_1_String_Prompt
}
if (($L_V_1_String_UserSelection -eq $null) -or (-not($L_V_1_String_UserSelection -match "\d")))
{
Throw
}
else
{
Try
{
#Replace placeholders, and save files to destination.
foreach($L_V_1_String_ConfigurationFileToCopy in $L_V_Array_String_ConfigurationFilesToCopy)
{
$L_V_1_FileInfo_ConfigurationFileToCopy = [io.FileInfo] $L_V_1_String_ConfigurationFileToCopy
$L_V_1_DestinationFilePath = "$L_V_1_String_DestinationPath\"+$L_V_1_FileInfo_ConfigurationFileToCopy.Name
Get-Content $L_V_1_FileInfo_ConfigurationFileToCopy -ErrorAction Stop | Foreach-Object {$_.replace($L_V_1_String_Placeholder , $L_V_HashTable_String_FileExtensions[[int]$L_V_1_String_UserSelection])} | Set-Content -Path $L_V_1_DestinationFilePath -ErrorAction Stop
Write-Host "File '$L_V_1_DestinationFilePath' has been copied!"
}
}
Catch
{
Write-Host $_.Exception.Message
Exit 1
}
}
}
Catch
{
Write-Host "That is not a valid choice!"
Exit 1
}
Write-Host "ComputerName: $L_V_1_String_DisplayComputerName"
Pause
Related
PowerShell Trying to compare .Length of $Variable in Excel Worksheets
I am working on piece of a powershell script to interact with a directory of excel documents. I have a for each loop to loop through each of the files in a directory open them to then copy and paste the DataBodyRange of listobject(1) to my main document. As stated in the title my issue is as follows: The If($shippedVal.Length -gt 0) statement will not proceed. As if the $shippedVal actually contains no value. When I try to Write-Host $shippedVal the IDE stalls. Here is the code in question: foreach($f in $clientPath){ $clientFile = $f.FullName $clientWorkbook = $excel.Workbooks.open($clientFile) $clientWorksheet = $clientWorkbook.Worksheets Write-Host $clientFile foreach ($ws in $clientWorksheet) { Write-Host $ws.Name + "Attempting List Object Count" If($ws.ListObjects.Count -gt 0) { Write-Host "Made it to copy range" $clientLR = ($ws.UsedRange.Rows.Count +1) $shippedVal = $ws.ListObjects(1).DataBodyRange.Cells(1) If($shippedVal.Length -gt 0) { Write-Host "Made it to line 41" $shippedBreakdown = $ws.ListObjects(1).DataBodyRange $pasteLoc = $mainWorksheet.Range("A" + "&" + $mainLR) $shippedBreakdown.Copy() | out-null $mainWorksheet.Paste($pasteLoc) $mainLR = ($mainWorksheet.UsedRange.Rows.Count + 1) Write-Host "Pasting DataBodyRange" } } } $clientWorkbook.Close } If you need any additional information please let me know.
How to fix line breaks?
I'm kind of new to PowerShell and I need to fix a bank statement file. The problem is that this file has a CNAB 240 standard, however, it comes with a line break, so we have to fix it manually. I have a script that makes this correction, however, I can't make it run alone because I need to use CMD to run PowerShell. My idea is to use this script in the task manager to look for the .txt file and fix it, does anyone have any ideas? Follow the script: if ($args.count -eq 0) { Write-Error "Please enter a file to read" return } $file = $args[0] if(![System.IO.File]::Exists($file)) { Write-Error "File not found: $file" return } $expectedsize = 240 $lastSize = 0 foreach($line in [System.IO.File]::ReadLines($file)) { if ($line.length -gt 0) { if (!($row.length -eq $expectedsize) -And (($row.length + $lastSize) -eq $expectedsize)) { Write-Host -NoNewLine $linha $lastSize += $line.length } else { if ($lastSize -gt 0) { Write-Host } Write-Host -NoNewLine $linha $lastSize = $line.length } } else { Write-Host } } The script above makes the normal correction of the file, however, I am not able to make it follow the steps below: Browse the txt file Validate that it does not have the 240 positions in each line And then make the correction
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
How to write an unknown number of values to one column using Export-Excel?
So, using Export-Excel, and dbatools, I'd like to pipe data from one loop to one column. All of the examples I've found so far, only show data you get from i.e. Get-Service Get-Service | Select-Object -Property Name, Status, DisplayName, ServiceName Which is going to create data on columns A, B, C and D on row 2, etc. I'm running multiple foreach loops: # Check if SQL Server Agent is running foreach ($instance in $sqlInstanceConfig) { $agent = Get-DbaAgentServer $instance $availabilityGroup = Get-DbaAvailabilityGroup -SqlInstance $instance if ($agent.SqlServerRestart) { Write-Log "INFO" "Agent is running on $instance." $ExecutionLogFullPath } elseif (!$agent.SqlServerRestart) { Write-Log "WARNING" "Agent is not running on $instance." $ExecutionLogFullPath } # Grab availability groups, and databases inside the groups foreach ($group in $availabilityGroup) { Write-Log "INFO" "For instance $($group.SqlInstance), Name: $($group.AvailabilityGroup) | Role: $($group.LocalReplicaRole) | Databases: $($group.AvailabilityDatabases)" $ExecutionLogFullPath } } I've also named the excel column "headers" to my liking: $exportExcel = Export-Excel -Path "C:\Users\janb\Desktop\testExport.xlsx" -ClearSheet -WorksheetName "TEST 123" -PassThru $ws = $exportExcel.Workbook.Worksheets["TEST 123"] $ws.Cells["A1"].Value = "SQL Instance" $ws.Cells["B1"].Value = "Instance status" $ws.Cells["C1"].Value = "Server agent status" $ws.Cells["D1"].Value = "DB disk space" $ws.Cells["E1"].Value = "DB in AG / Synchronizing" $ws.Cells["F1"].Value = "Primary replica" $ws.Cells["G1"].Value = "DB full & log backup" $ws.Cells["H1"].Value = "Log backup oversize" $ws.Cells["I1"].Value = "Backup disk space" $ws.Cells["J1"].Value = "Job name" $ws.Cells["K1"].Value = "Jobs correctly enabled" $ws.Cells["L1"].Value = "Enabled job running / Last run date" But now I'm stuck on piping data to A2->A*, B2->B*, etc. based on the number of results I get from my previous loops. So what I'm trying to accomplish is, how do I write data from $instance to A2 and down without hardcoding the column values, because they can change at any time.
According to my comment: Nobody but you knows how to map your Excel column headers to $instance/$group properties. While your way could work it's IMO to cumbrsome dealing with every cel(row,col). I'd create a [PSCustomObject] inside the 2nd foreach and let Export-Excel Do the tedious work submitting the collected data in one go. Something like this could do: ## Q:\Test\2019\09\05\SO_57803106.ps1 # Check if SQL Server Agent is running $Data = foreach ($instance in $sqlInstanceConfig) { $agent = Get-DbaAgentServer $instance $availabilityGroup = Get-DbaAvailabilityGroup -SqlInstance $instance if ($agent.SqlServerRestart) { Write-Log "INFO" "Agent is running on $instance." $ExecutionLogFullPath } elseif (!$agent.SqlServerRestart) { Write-Log "WARNING" "Agent is not running on $instance." $ExecutionLogFullPath } # Grab availability groups, and databases inside the groups foreach ($group in $availabilityGroup) { Write-Log "INFO" "For instance $($group.SqlInstance), Name: $($group.AvailabilityGroup) | Role: $($group.LocalReplicaRole) | Databases: $($group.AvailabilityDatabases)" $ExecutionLogFullPath [PSCustomObject]#{ "SQL Instance" = $group.SqlInstance "Instance status" = "what" "Server agent status" = "ever" "DB disk space" = "you" "DB in AG / Synchronizing" = "like" "Primary replica" = "to" "DB full & log backup" = "map" "Log backup oversize" = "to" "Backup disk space" = "the" "Job name" = "excel" "Jobs correctly enabled" = "column" "Enabled job running / Last run date" = "here" } } } ## optionally preview Data in a gridview # Data | Out-GridView $ExcelFile = Join-Path ([Environment]::GetFolderPath('Desktop')) "testExport.xlsx" $Data | Export-Excel -Path $ExcelFile -ClearSheet -WorksheetName "TEST 123" -AutoSize
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: