Powershell terminates early after exiting do-until loop - powershell

I'm working on a Powershell script, and I'm trying to accept custom user input from a list of options. The issue I'm running into is that when the exit condition (typing the character 'd') is met, the script terminates and does not execute the remaining code (which is supposed to copy shortcuts based on a user-defined array, $OfficeProgramNames)
#OfficeShortcuts -- Creates shortcuts for the main four Office 2016 applications.
#Optional command-line parameters that can be passed to create icons for All (-a) icons, or a CUSTOM set of icons (-c)
param([switch] $a, [switch] $c)
#Creates a shortcut on the desktop that copies from the start menu shortcuts.
function CreateOfficeDesktopShortcut([string] $ShortcutName)
{
Copy-Item -Path "C:\ProgramData\Microsoft\Windows\Start Menu\Programs\$ShortcutName.lnk" "$env:USERPROFILE\Desktop"
}
function Show-Menu
{
Clear-Host
Write-Host "Select which items you would like placed on the desktop:"
Write-Host "1: Word"
Write-Host "2: Excel"
Write-Host "3: PowerPoint"
Write-Host "4: Outlook"
Write-Host "5: Access"
Write-Host "6: OneNote"
Write-Host "7: Publisher"
Write-Host "D: Press 'D' when done."
}
function GetCustomUserSelection($OfficeProgramNames)
{
$OfficeProgramNames = #()
do
{
Clear-Host
Show-Menu
Write-Host "You have selected: $OfficeProgramNames"
$foo = Read-Host "Please make a selection:"
switch($foo)
{
'1'{
$OfficeProgramNames += "Word*"
}
'2'{
$OfficeProgramNames += "Excel*"
}
'3'{
$OfficeProgramNames += "PowerPoint*"
}
'4'{
$OfficeProgramNames += "Outlook*"
}
'5'{
$OfficeProgramNames += "Access*"
}
'6'{
$OfficeProgramNames += "OneNote*"
}
'7'{
$OfficeProgramNames += "Publisher*"
}
'8'{
$OfficeProgramNames += "Skype*"
}
'd'{
return
}
}
}
until ($foo -eq 'd')
Write-Host "Now do something else"
}
#Loop through the array of Shortcut names, and create a shortcut with a matching alias
function SelectOfficeShortcuts($arg1, $arg2)
{
[string[]] $OfficeProgramNames = #("Word", "Excel", "PowerPoint", "Outlook")
if ($a -eq $True)
{
$OfficeProgramNames += "Access", "OneNote*", "Publisher"
}
elseif($c -eq $True)
{
$OfficeProgramNames = #()
GetCustomUserSelection $OfficeProgramNames
}
for ($i=0; $i -lt $OfficeProgramNames.Length; $i++)
{
CreateOfficeDesktopShortcut $OfficeProgramNames[$i]
}
}
SelectOfficeShortcuts($a, $c)
I have tried inserting a couple of "Write-Host" commands to debug and see where the code stops terminating. I'm rather baffled and I'm having trouble seeing where the control flow is, so if someone could help me with some clarification, that would be greatly appreciated!
For further clarity, the output is as follows(slightly modified since there are some Clear-Host commands):
PS C:\Users\atroach\Documents\GitHub\OfficeShortcuts> .\OfficeShortcuts.ps1 -c
...
Select which items you would like placed on the desktop:
1: Word
2: Excel
3: PowerPoint
4: Outlook
5: Access
6: OneNote
7: Publisher
D: Press 'D' when done.
You have selected: Excel PowerPoint
Please make a selection: d
PS C:\Users\username\Documents\GitHub\OfficeShortcuts>
"SelectOfficeShortcuts ($a, $c)" is called at the end of the script, where $a and $c are switch parameters for the script.

Related

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 Switch Executing Again On Quit

I'm attempting to write a PowerShell menu with multiple switches, but can't figure out why previously run commands will execute again on quit. Any help would greatly be appreciated. The code I have so far is as follows:
function Show-Menu {
param (
[string]$Title = 'Menu'
)
Clear-Host
Write-Host "`n============= $Title =============`n"
Write-Host "Press 'A' to run all commands"
Write-Host "Press '1' to run foo"
Write-Host "Press '2' to run bar"
Write-Host "Press 'Q' to quit"
}
do {
Show-Menu
$Selection = Read-Host "`nPlease make a selection"
switch ($Selection) {
'A' {
$Actions = #('foo', 'bar')
}
'1' {
$Actions = "foo"
}
'2' {
$Actions = "bar"
}
}
switch ( $Actions ) {
'foo' {
Write-Host "foo executed"
Start-Sleep -Seconds 2
}
'bar' {
Write-Host "bar executed"
Start-Sleep -Seconds 2
}
}
}
until ($Selection -eq 'q')
Simplify.
Instead of saving actions in a variable, and then taking another step of evaluating that variable... do the actions.
Instead of having a loop that checks the exit condition in a separate location (i.e. using until), use an endless loop and an explicit break. This helps keeping the logic in one place.
function foo { Write-Host "foo"; Start-Sleep -Seconds 1 }
function bar { Write-Host "bar"; Start-Sleep -Seconds 1 }
:menuLoop while ($true) {
Clear-Host
Write-Host "`n============= Menu =============`n"
Write-Host "Press 'A' to run all commands"
Write-Host "Press '1' to run foo"
Write-Host "Press '2' to run bar"
Write-Host "Press 'Q' to quit"
switch (Read-Host "`nPlease make a selection") {
'A' { foo; bar }
'1' { foo }
'2' { bar }
'Q' { break menuLoop }
}
}
Your approach does not work correctly because in your code, pressing Q does not immediately exit the loop, and $Actions is still filled from the last iteration.
That's another lesson: Variable values don't reset on their own in loops. Always set your variables to $null at the start of the loop to get a clean state.
Note the :mainLoop label. Without it, break would only apply to the switch statement itself. See MSDN
That being said, PowerShell has a pretty nifty menu system built-in that you can use.
using namespace System.Management.Automation.Host
function foo { Write-Host "foo"; Start-Sleep -Seconds 1 }
function bar { Write-Host "bar"; Start-Sleep -Seconds 1 }
# set up available choices, and a help text for each of them
$choices = #(
[ChoiceDescription]::new('run &all commands', 'Will run foo, and then bar')
[ChoiceDescription]::new('&1 run foo', 'will run foo only')
[ChoiceDescription]::new('&2 run bar', 'will run bar only')
[ChoiceDescription]::new('&Quit', 'aborts the program')
)
# set up script blocks that correspond to each choice
$actions = #(
{ foo; bar }
{ foo }
{ bar }
{ break menuLoop }
)
:menuLoop while ($true) {
$result = $host.UI.PromptForChoice(
"Menu", # menu title
"Please make a selection", # menu prompt
$choices, # list of choices
0 # default choice
)
& $actions[$result] # execute chosen script block
}
Run this in PowerShell ISE and regular PowerShell to see how it behaves in each environment.
This is happening because of your do...until loop. You're committing to executing the loop before the user input is taken. Since this is the case, $Actions is already set from the previous iteration of the loop, so it runs what had previously run.
This means that if you don't override $Actions for every iteration of the loop, this will happen for other commands as well.
A simple fix to this would be to add a case for q to set $Actions to something that isn't in the switch statement evaluating $Actions. In this case, an empty string should do.
If you need it to work in a similar way for other commands as well, rather than having a case specifically for q, you can use the default case to set the $Actions variable.

PowerShell Show Menu

I have the next code written in PowerShell.
When I run the code, the Menu is shown, I enter the option 1..9 and the selected option is not called, the menu is shown again and again(see the screenshot).
When I enter an option I want to be called that function related to each option entered then to display the message "The function has been called" and also to display the menu to enter a new option. (see the scrennshot - code in C++)
Any idea ?
function PORII
{
Write-Host " PORII was called"
}
function DXD-BODY
{
Write-Host " DXD BODY was called"
}
function DXD-PAINT
{
Write-Host " DXD PAINT was called"
}
function DXD-PTO
{
Write-Host " DXD PTO was called"
}
function DXD-TCF
{
Write-Host " DXD TCF was called"
}
function FIS-SERVERS
{
Write-Host " FIS SERVERS was called"
}
function SERVERS
{
Write-Host " SERVERS was called"
}
function Acronis
{
Write-Host " Acronis was called"
}
function Menu
{
param([string]$Title = 'Menu')
Write-Host " ==================== $Title ==================== "
while (1)
{
Clear-Host
Write-Host " Press 1 for PORII: "
Write-Host " Press 2 for DXD BODY: "
Write-Host " Press 3 for DXD PAINT: "
Write-Host " Press 4 for DXD PTO: "
Write-Host " Press 5 for DXD TCF: "
Write-Host " Press 6 for FIS SERVERS: "
Write-Host " Press 7 for SERVERS: "
Write-Host " Press 8 for Acronis Images: "
Write-Host " Press 9 for Exit: "
$a = Read-Host -Prompt "`n Enter your option "
if (($a -eq 1) -or ($a -eq 2) -or ($a -eq 3) -or ($a -eq 4) -or ($a -eq 5) -or ($a -eq 6) -or ($a -eq 7) -or ($a -eq 8) -or ($a -eq 9))
{
switch($a)
{
1{PORII}
2{DXD-BODY}
3{DXD-PAINT}
4{DXD-PTO}
5{DXD-TCF}
6{FIS-SERVERS}
7{SERVERS}
8{Acronis-Images}
9{Exit}
}
}
else
{
continue
}
}
}
Menu
Your functions are being called, but that isn't obvious because Clear-Host is called right after, discarding the called function's output.
Apart from that, your code can be streamlined:
Instead of the if (($a -eq 1) -or ($a -eq 2) ... conditional, you can add a default branch to your switch statement.
Also note that Read-Host always returns a string, whereas your conditionals operate on numbers; thanks to PowerShell's automatic type conversions, this isn't a problem in your particular case (with explicit or implied equality comparison), but it's something to keep in mind.
Through the use of a hash table you can simplify your function while making it easier to maintain.
Function Menu {
param([string]$Title = 'Menu')
while ($TRUE) {
$OptionHT = #{
1="PORII"
2="DXD-BODY"
3="DXD-PAINT"
4="DXD-PTO"
5="DXD-TCF"
6="FIS-SERVERS"
7="SERVERS"
8="Acronis-Images"
9="Exit"
}
Write-Host " ==================== $Title ==================== "
For ($Cntr = 1 ; $Cntr -lt $($OptionHT.Count) + 1; $Cntr++) {
Write-Host "Press $Cntr for $($OptionHT.$($Cntr)):"
}
$a = Read-Host -Prompt "`n Enter your option "
If (($a.length) -eq 1 -and ([byte][char]$a) -ge 49 -and
([byte][char]$a) -le 57) {
& ($OptionHT.[int]$a)
}
} # End While ($True)
} # End Function Menu
By placing your options in the hash table you only have a single point to make changes for called function names.
The if statement vs switch eliminates any value other than a single single number from 1 to 9 (note use of ASCII values to verify number input) from being processed. And since we have eliminated invalid inputs a single statement can be used to search the hash table for the function to execute.
UPDATE: Per the comments below you'll have to either trap the EXIT (9) with an If statement and exit or Create another Function and call it something like Exit-Program and place the Exit command there, I tested a function and it works.
Note: I didn't clear the console between writes of the menu so you could see your selection as mentioned in the comments you can add it where you deem necessary.
HTH

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"
}

Option Menu in Powershell: Continue After Loop

I would like to create a simple menu in powershell. If you look at my example bellow, I am waiting in do loop until the key 'q' is pressed. If user hits '1' or '2', it should do something specified in switch, leave the loop and continue in script by showing "Another part of code".
If break is used in switch, it doesn't leave the loop. If return is is used, the entire script is exited.
Here's the code:
function Select-Machine
{
cls
Write-Host "1: Option1"
Write-Host "2: Option2"
Write-Host
}
Do{
Select-Machine
$input = Read-Host "Choose Virtual Machine"
switch ($input)
{
'1' {
#1: Do Something
break
}
'2' {
#2: Do Something
break
}
}
}until($input -eq 'q')
Write-Host "Another part of code"
When you use the break statement within a switch you only exit the switch (e.g it doesn't attempt to evaluate any of the other options). You could instead replace where you have break with $input = 'q' and this will result in the loop then completing.
Alternatively you could have an if after the switch to check a list of valid options and do a break at that point, which would then operate on the loop. E.g:
if ($input -in '1','2') { break }
What I would do is use a Do/While loop instead of a Do/Until loop. Also, move the switch outside of the loop, there's no need to have it in there.
Do{
Select-Machine
$Response = Read-Host "Choose Virtual Machine"
}While($Response -notin 1,2,'q')
Switch($Response){
1 {
#Do 1 stuff!
continue
}
2 {
#Do 2 stuff!
continue
}
}
I've done this a number of times in scripts that I know are going to be interactive and have a menu for options. Towards that end I even wrote a function to make decent looking menus for me:
Function MenuMaker{
param(
[parameter(Mandatory=$true)][String[]]$Selections,
[switch]$IncludeExit,
[string]$Title = $null
)
$Width = if($Title){$Length = $Title.Length;$Length2 = $Selections|%{$_.length}|Sort -Descending|Select -First 1;$Length2,$Length|Sort -Descending|Select -First 1}else{$Selections|%{$_.length}|Sort -Descending|Select -First 1}
$Buffer = if(($Width*1.5) -gt 78){[math]::floor((78-$width)/2)}else{[math]::floor($width/4)}
if($Buffer -gt 6){$Buffer = 6}
$MaxWidth = $Buffer*2+$Width+$($Selections.count).length+2
$Menu = #()
$Menu += "╔"+"═"*$maxwidth+"╗"
if($Title){
$Menu += "║"+" "*[Math]::Floor(($maxwidth-$title.Length)/2)+$Title+" "*[Math]::Ceiling(($maxwidth-$title.Length)/2)+"║"
$Menu += "╟"+"─"*$maxwidth+"╢"
}
For($i=1;$i -le $Selections.count;$i++){
$Item = "$(if ($Selections.count -gt 9 -and $i -lt 10){" "})$i`. "
$Menu += "║"+" "*$Buffer+$Item+$Selections[$i-1]+" "*($MaxWidth-$Buffer-$Item.Length-$Selections[$i-1].Length)+"║"
}
If($IncludeExit){
$Menu += "║"+" "*$MaxWidth+"║"
$Menu += "║"+" "*$Buffer+"X - Exit"+" "*($MaxWidth-$Buffer-8)+"║"
}
$Menu += "╚"+"═"*$maxwidth+"╝"
$menu
}
Then I can just call it like:
MenuMaker -Selections 'SQL Server','Exchange Server' -Title 'Choose Virtual Machine' -IncludeExit
And it will spit back:
╔═══════════════════════════════════╗
║ Choose Virtual Machine ║
╟───────────────────────────────────╢
║ 1. SQL Server ║
║ 2. Exchange Server ║
║ ║
║ X - Exit ║
╚═══════════════════════════════════╝
To put that in context to your code, and my suggestions, it would look like:
Do{
cls
MenuMaker -Selections 'SQL Server','Exchange Server' -Title 'Choose Virtual Machine' -IncludeExit
$Response = Read-Host "Choose Virtual Machine"
}While($Response -notin 1,2,'x')
Edit: I just realized that my while vs until argument is pointless, all it does is flip the -in to -notin. The point is that you need to validate their response against the possible options, not just against 'q'.
You can use labels with break to jump out from the outer loop. I don't think it's especially beautiful, much better is to turn until condition true. But here we go. Oh, and please don't use $input variable name, that's PowerShell's automatic variable reserved for, em, input.
Write-Host "Begin"
:menu Do {
$i = Read-Host "Select"
switch ($i) {
'1' {
write-host "Do 1"
break menu
}
'2' {
write-host "Do 2"
break menu
}
}
} until($i -eq 'q')
Write-Host "End"
I think you can simplify this a bit and remove the function:
Do{
#Select-Machine
write-Host "input 1 or 2 - Choose Virtual Machine"
if ($input = Read-Host "type in 1 or 2"){
switch ($input)
{
'1' {
#1: Do Something
Write-host '1 doing something'
break
}
'2' {
#2: Do Something
write-host '2 doing something'
break
}
}}
}until($input -eq 'q')
Write-Host "Another part of code"