Use switch menu and pass argument to function - powershell

I have a script I am working on which has menu function
DO {
$selection = Read-Host ">"
SWITCH ($selection) {
'-h' {GET_HELP}
'refresh' {REFRESH_LIST}
'start' {INSTANCE_START}
'stop' {'STOPPING INTANCE'}
'q' {BREAK}
DEFAULT {'ENTER -h FOR HELP'}
}} until ($selection -eq 'q')
What I am looking to do with this script is start and stop virtual machine. It lists out all machines and in the menu I would like the user to issue the command as follows:
start <hostName>
The start being the command in the switch menu and the <hostName> which will always be different is the argument being passed to the INSTANCE_START function. My first thought is to use split to split up the inputted string and have the first part of the split reference what switch case to go to and the second part be a variable passed to the function. Is this the best way ? or is there an easier approach to this and menus ?

Switch statement has its own switches that modify its behavior.
Use -wildcard and add * in place of the variable part:
switch -wildcard ($selection) {
'-h' { GET_HELP; break }
'refresh' { REFRESH_LIST; break }
'start *' { INSTANCE_START; break }
'stop *' { 'STOPPING INTANCE'; break }
'q' { break }
default { 'ENTER -h FOR HELP' }
}
Switch is case-insensitive like all basic string operators in PS (-eq, -ne, -like, -match),
so there's no need to alter the letter case.

You can use expressions in switch cases:
switch($selection.ToLower()){
{$_ -like "start *"} { INSTANCE_START $($_.replace("start ","")) }
}

Related

powershell regex replace using variable parameters when calling replace

I wondering how to make the following powershell code work with regular expressions. For example I want to call it like this:
rename_file -OneFile proj1_file.txt -Replace0 "^proj1_" -Replace1 "mynewproj_"
The problem is that as soon as I add the regular expression caret (^) my function stops matching for the replace function... If I remove the caret anchor the code works... but is dangerous because its not anchored to the front of the string... I really want the regular expressions chracters to work when passing the string in to this function...
Here's my powershell code...
$script:TotalRename = 0
function rename_file {
param(
[string]$OneFile,
[string]$Replace0,
[string]$Replace1
)
#$NewName = $OneFile.Replace($Replace0, $Replace1)
$NewName = $OneFile -replace $Replace0, $Replace1
if ($NewName -eq $OneFile) {
write-host "NoChange $OneFile"
return
}
$script:TotalRename = $script:TotalRename + 1
write-host "rename $OneFile => $NewName"
}

Powershell Switch Break Label not being executed

As part of a larger script I have implemented the switch detailed below. The intention is that when the script is executed a user can choose to either
enter users to be migrated on-screen, or
import from a file.
If the import from file option is selected - I want to test the file is present - if not I want to break out and go back to the switch label :choose. However when I select the import from file option and provide a non-existent path the script continues and does not break or return to the label. Where am I going wrong?
$chooseInputMethod = #"
This script migrates one or more user accounts between two trusted domains in a forest e.g. from domain1 to domain2 (or vice versa)
Select method to specify user(s) to migrate:
1. Enter name(s) on-screen (default)
2. Import name(s) from file
Enter selection number
"#
$choosePath = #"
Enter path to file..
Notes
- Filename:
If file is located in script directory ($pwd) you can enter the filename without a path e.g. users.txt
- No quotation marks:
DO NOT put any quotes around the path even if it contains spaces e.g. e:\temp\new folder\users.txt
Enter path or filename
"#
$enterUsernames = #"
Enter username(s) seperate each with a comma e.g. test1 or test1,test2,test3`n`nEnter name(s)
"#
cls
:choose switch (Read-Host $chooseInputMethod) {
1 { cls; $usersFromScreen = Read-Host $enterUsernames }
2 {
cls;
$usersFromFile = Read-Host $choosePath;
if (-not (Test-Path $usersFromFile -PathType Leaf)) {
break choose
}
}
default { cls; $usersFromScreen = Read-Host $enterUsernames }
}
Write-Host "hello"
From the documentation for break:
In PowerShell, only loop keywords, such as Foreach, For, and While can have a label.
So, switch, even though it has looping capabilities, is not considered a loop in this case.
However, in this case I don't see why break without a label would not suffice.
A break to the switch will exit it. There's only one loop (switch, which works on arrays), so there's no difference between a plain break and breaking to the switch. You seem to want the switch inside another loop to repeat it.
# only runs once
:outer while (1) {
'outer'
while (1) {
'inner'
break outer
}
}
outer
inner
Demo of switch with a label and looping. The documentation isn't perfect.
# only runs once
:outer switch (1..10) {
default {
'outer'
foreach ($i in 1..10) {
'inner'
break outer
}
}
}
outer
inner
Another switch demo.
switch (1..4) {
{ $_ % 2 } { "$_ is odd" }
default { "$_ is even" }
}
1 is odd
2 is even
3 is odd
4 is even

If statement - Four variables, only run if one true

I have the following which doesn't allow both variables to be enabled (boolean true value):
If (($Variable1) -and ($Variable2)) {
Write-Warning "Both variables have been enabled. Modify script to enable just one."
Pause
Break
}
This works great, however, how would I ensure only one is ever enabled when 4 possible variables exist? I'm thinking a combination of -and & -or?
You can add the boolean values and check their count:
If (([bool]$Variable1 + [bool]$Variable2 + [bool]$Variable3) -ne 1) {
...
}
but of course you have to make sure that these can actually be cast to boolean.
That's what "exclusive or" (xor) is for:
If ($Variable1 -xor $Variable2 -xor $Variable3) {
....
}
About logical operators in Powershell
Cannot think of a way to do this that avoids using a counter. You have to check the value of each variable and keep count of how many are $true.
$trueCount = 0
($variable1, $variable2, $variable3, $variable4) | % { if ($_ ) { $trueCount++} }
if ($trueCount -eq 1) {
write-host "only one variable true"
}
else {
write-host "condition not met"
}

Appending variable to string to pull value of existing variable

I'm currently learning PowerShell and I can't work out how to combine a string and a variable to pull information from an existing variable.
The user input will just be a number, so 1,2,3 etc. which I need to append to the end of $option which will pull the title information from the variable $optionX.
So far everything I've tried just interprets it as a string and print $OptionX into the console, as opposed to the value held by $OptionX.
So for example:
function Title{
Write-host "$OptionName for:"$computerSystem.Name -BackgroundColor DarkCyan
}
function GetMenu {
# Set the menu options
$Option1 = "1) System Information"
# Get menu selection
$Navigation = Read-Host "Enter Selection"
ToolBox
}
function ToolBox{
Clear-Host
switch ($Navigation){
1 { #Script 1
Title
}
You can do what you do in the self-answer. I would suggest using a hash-map for it though - seems cleaner to me. (I have no idea what the $computersystem.Name-part is, so I just left it in):
function Title{
Write-host "$($Options[$Navigation]) for:"$computerSystem.Name -BackgroundColor DarkCyan
}
function GetMenu {
# Set the menu options
$Options = #{
"1" = "1) System Information"
"2" = "2) Something else"
}
# Get menu selection
$Navigation = Read-Host "Enter Selection"
ToolBox
}
function ToolBox{
Clear-Host
switch ($Navigation){
1 { #Script 1
Title
}
}
}
For the rest of your script I can see that you are using Global Variables extensively, which I would avoid (it will confuse you, makes it harder to understand what is going on, and many other reasons not to use them). Look into using parameters for your functions, using the snippet menu (CTRL+J) in Powershell ISE will make a quick function skeleton for you. When you want to develop further in Powershell functions look into the Cmdlet (advanced function) template in the same menu.
I figured out how to do it, I'm not sure if it's the best method but it does what I need it to do.
function Title{
$OptionCombine = "Option"+$Navigation
$OptionName = Get-variable $OptionCombine -ValueOnly
Write-host "$OptionName for:"$computerSystem.Name -BackgroundColor DarkCyan
}

$prompt = ($defaultValue,$prompt)[[bool]$prompt] - emulating a ternary conditional in PowerShell

I'm learning to write scripts with PowerShell, and I found this code that will help me with a project The example comes from Is there a one-liner for using default values with Read-Host?.
$defaultValue = 'default'
$prompt = Read-Host "Press enter to accept the default [$($defaultValue)]"
$prompt = ($defaultValue,$prompt)[[bool]$prompt]
I think I understand that $prompt = ($defaultValue,$prompt) is creating a two-element array and that the [bool] part is forcing the $prompt data type to Boolean, but I don’t understand what this third line of code does as a whole.
This is a common programming pattern:
if (user entered a price)
{
price = user entered value
}
else
{
price = default value
}
and because that is quite common, and also long winded, some languages have a special ternary operator to write all that code much more concisely and assign a variable to "this value or that value" in one move. e.g. in C# you can write:
price = (user entered a price) ? (user entered value) : (default value)
# var = IF [boolean test] ? THEN (x) ELSE (y)
and the ? assigns (x) if the test is true, and (y) if the test is false.
In Python, it's written:
price = (user entered value) if (user entered a price) else (default value)
And in PowerShell, it's written:
# you can't have a ternary operator in PowerShell, because reasons.
Yeah. No nice short code pattern allowed.
But what you can do, is abuse array-indexing (#('x', 'y')[0] is 'x' and #('x', 'y')[1] is 'y' and ) and write that ugly and confusing code-golf line:
$price = ($defaultValue,$userValue)[[bool]$UserEnteredPrice]
# var (x,y) is an array $array[ ] is array indexing
(0,1) are the array indexes of the two values
[bool]$UserEnteredPrice casts the 'test' part to a True/False value
[True/False] used as indexes into an array indexing makes no sense
so they implicitly cast to integers, and become 0/1
# so if the test is true, the $UserValue is assigned to $price, and if the test fails, the $DefaultValue is assigned to price.
And it behaves like a ternary operator, except it's confusing and ugly and in some situations it will trip you up if you're not careful by evaluating both array expressions regardless of which one is chosen (unlike real ? operators).
Edit: What I should really add is a PowerShell form I prefer - you can assign the result of an if test directly in PowerShell and do:
$price = if ($userValue) { $userValue } else { $DefaultValue }
# ->
$prompt = if ($prompt) { $prompt } else { $DefaultValue }
Casting $prompt to [bool] produces a value of $true or $false depending on whether the variable is empty ($null or empty string both become $false) or not (non-emtpy strings become $true).
[bool]'' → $false
[bool]'something' → $true
Using that boolean value in the index operator then implicitly casts the value to an integer where $false becomes 0 and $true becomes 1, hence selecting the first or second element of the array.
[int]$false → 0
[int]$true → 1
($defaultValue,$prompt)[0] → $defaultValue
($defaultValue,$prompt)[1] → $prompt
To complement the great answers given by Ansgar Wiechers and by TessellatingHeckler:
It would be great if PowerShell had operators for ternary conditionals and null-coalescing, such as follows (applied to the example in the question):
Update: PowerShell (Core) 7+ now does have these operators.
# Ternary conditional
# Note: does NOT work in *Windows PowerShell*,
# only in PowerShell (Core) v7+
$prompt = $prompt ? $prompt : $defaultValue
# Or, more succinctly, with null coalescence:
# Note: does NOT work in *Windows PowerShell*,
# only in PowerShell (Core) v7+
# (Note: This example assumes that $prompt will be $null in the default
# case, whereas the code in the question actually assigns the
# empty string to $prompt if the user just presses Enter.)
$prompt = $prompt ?? $defaultValue
Unfortunately, these expressive constructs are not part of Windows PowerShell, the legacy edition of PowerShell, which will see no new functionality.
Below are adapted versions of the functions from a 2006 PowerShell Team blog post with associated alias definitions, whose use then allows the following solution:
# Ternary conditional - note how the alias must come *first*
# Note: Requires the function and alias defined below.
$prompt = ?: $prompt $prompt $defaultValue
# Or, more succinctly, with null coalescence - note how the alias must come *first*
# Note: Requires the function and alias defined below.
$prompt = ?? $prompt $defaultValue
Source code:
Note that the actual functions are quite short; it is the comment-based help that makes this listing lengthy.
Set-Alias ?: Invoke-Ternary -Option AllScope
<#
.SYNOPSIS
Emulation of a ternary conditional operator.
.DESCRIPTION
An emulation of the still-missing-from-the-PS-language ternary conditional,
such as the C-style <predicate> ? <if-true> : <if-false>
Because a function is used for emulation, however, the function name must
come first in the invocation.
If you define a succinct alias, e.g., set-alias ?: Invoke-Ternary,
concise in-line conditionals become possible.
To specify something other than a literal or a variable reference, pass a
script block for any of the tree operands.
A predicate script block is of necessity always evaluated, but a script block
passed to the true or false branch is only evaluated on demand.
.EXAMPLE
> Invoke-Ternary { 2 -lt 3 } 'yes' 'no'
Evaluates the predicate script block, which outputs $true, and therefore
selects and outputs the true-case expression, string 'yes'.
.EXAMPLE
> Invoke-Ternary $false { $global:foo = 'bar' } { Get-Date }
Outputs the result of executing Get-Date.
Note that the true-case script block is NOT evaluated in this case.
.NOTES
Gratefully adapted from http://blogs.msdn.com/powershell/archive/2006/12/29/dyi-ternary-operator.aspx
#>
function Invoke-Ternary
{
[CmdletBinding()]
param($Predicate, $Then, $Otherwise = $null)
if ($(if ($Predicate -is [scriptblock]) { & $Predicate } else { $Predicate })) {
if ($Then -is [ScriptBlock]) { & $Then } else { $Then }
} else {
if ($Otherwise -is [ScriptBlock]) { & $Otherwise } else { $Otherwise }
}
}
Set-Alias ?? Invoke-NullCoalescence -Option AllScope
<#
.SYNOPSIS
Emulation of a null-coalescence operator.
.DESCRIPTION
An emulation of a null-coalescence operator such as the following:
<expr> ?? <alternative-expr-if-expr-is-null>
Because a function is used for emulation, however, the function name must
come first in the invocation.
If you define a succinct alias, e.g., set-alias ?? Invoke-NullCoalescence,
concise in-line null-coalescing becomes possible.
To specify something other than a literal or a variable reference, pass a
script block for any of the two operands.
A first-operand script block is of necessity always evaluated, but a
second-operand script block is only evaluated on demand.
Note that only a true $null value in the first operand causes the second
operand to be returned.
.EXAMPLE
> Invoke-NullCoalescence $null '(empty)'
Since the first operand is $null, the second operand, string '(empty)', is
output.
.EXAMPLE
> Invoke-NullCoalescence '' { $global:foo = 'bar' }
Outputs the first operand, the empty string, because it is not $null.
Note that the second-operand script block is NOT evaluated in this case.
.NOTES
Gratefully adapted from http://blogs.msdn.com/powershell/archive/2006/12/29/dyi-ternary-operator.aspx
#>
function Invoke-NullCoalescence
{
[CmdletBinding()]
param($Value, $Alternative)
if ($Value -is [scriptblock]) { $Value = & $Value }
if ($null -ne $Value) {
$Value
} else {
if ($Alternative -is [ScriptBlock]) { & $Alternative } else { $Alternative }
}
}