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
Related
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
}
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 ","")) }
}
I m trying to compare the Active directory sites with SCCM boundaries, by using the below powershell scripts, but its not giving the output as expected.
There are 3 AD sites actually available in SCCM, however the script gives me an output that there is no AD sites available in SCCM boundaries.
$sites = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest().Sites
$CMBoundary = Get-CMBoundary | select value
foreach ($adsite in $sites.name) {
foreach ($cmb in $CMBoundary.value ) {
if (($cmb | select value) -eq ($adsite | select name)) {
"$adsite available in CM"
}
else { "$adsite is NOT in CM $cmb" }
}
}
Could someone please help me on this.
Think I see the issue.
On line 3, you're selecting the .Name property from $sites and storing it in $adsite, for each step of your loop.
foreach ($adsite in $sites.name) {
However, you then also attempt to take the .Name property again, with this line:
if (($cmb | select value) -eq ($adsite | select name))
This won't work. You've set $adsite to be equal to whatever was in $sites.Name, but that doesn't give a .Name property to $adsite.
Try this again, this time with the second Select statement removed. The reason this was failing is that there wouldn't be anything to compare against. I've revised your code to remove this logic, let me know if it works.
foreach ($adsite in $sites.name) {
foreach ($cmb in $CMBoundary.value ) {
if ($cmb -eq $adsite) {
"$adsite available in CM"
}
else { "$adsite is NOT in CM $cmb" }
}
}
Be warned, however, that if your boundary name doesn't exactly match an AD Site name, that this code will give you a lot of false positives.
Trying to make 2 loops while reading 2 files, the purpose to take 1 value from first file compare it to 1,2,3rd.. etc values in second file, then go back to outside loop, take 2nd value and compare to all the values in second file sequencially, and so on.
Problem is it only runs comparison on first value in first file and stops.
Code:
Get-ExecutionPolicy
$main_list = [System.IO.File]::OpenText('c:\ping-workstations\main_list.txt')
$retired = [System.IO.File]::OpenText('c:\ping-workstations\retired.txt')
$main_i = 1
$retired_i = 1
[string]$main_list_line
[string]$retired_list_line
try
{
while(!$main_list.EndOfStream)
{
$main_list_line = $main_list.ReadLine()
while(!$retired.EndOfStream)
{
$retired_list_line = $retired.readline()
if($main_list_line -eq $retired_list_line)
{
write-host "`n`n`n match found for $main_list_line in main list `n`n`n`n"
add-content "c:\ping-workstations\matches-through-text-files.txt" "$main_list_line`n"
}
write-host "main iteration: $main_i / inside iteration: $retired_i`n - main value: $main_list_line ---- inside value: $retired_list_line`n`n"
$retired_1++
}
$main_i++
}
}
finally
{
$main_list.close()
$retired.close()
}
You need to re-open the $retired file after the inner loop finishes. As it is written now, once the inner loop traverses the $retired file, $retired.EndOfStream will forever remain true.
try
{
while(!$main_list.EndOfStream)
{
$main_list_line = $main_list.ReadLine()
while(!$retired.EndOfStream)
{
$retired_list_line = $retired.readline()
if($main_list_line -eq $retired_list_line)
{
write-host "`n`n`n match found for $main_list_line in main list `n`n`n`n"
add-content "c:\ping-workstations\matches-through-text-files.txt" "$main_list_line`n"
}
write-host "main iteration: $main_i / inside iteration: $retired_i`n - main value: $main_list_line ---- inside value: $retired_list_line`n`n"
$retired_1++
}
$main_i++
$retired.close()
$retired = [System.IO.File]::OpenText('c:\ping-workstations\retired.txt')
}
}
finally
{
$main_list.close()
$retired.close()
}
As a side note, have you looked into using the Powershell ISE? It has debugging capabilities; by setting a breakpoint and stepping through your code, I was able to spot the problem quickly.
I'm creating a series of new PSObjects, from a CSV import, and then adding them to $new. I'm using a switch to try and set the value for the "Notes" property, as the object is being created\added, and I've run into something 'hinky'.
When I run this...
$import = Import-Csv c:\somerandom.csv
$new = #()
foreach ($Item in $Import) {
$obj = New-Object PsObject -Property #{
Name = $item.Name
Description = $Item.Description
Quantity = $Item.Quantity
Vendor = $Item.Vendor
SubCategory = "Misc"
Notes = ""
}
switch ($obj.Name) {
"iPod" { $obj.Notes = "Burn with the rest of the Apple garbage"}
"nVidia GTX 780ti" { $obj.Notes = "Steal immediately!" }
default { $obj.Notes= "Sorry man... I have no idea what that is"}
}
$new += $obj
}
... it works as expected. All of the entries from $import, are recreated in $new, with the addition of my "SubCategory" and "Notes" noteproperties (iPod gets burn tag, 780ti slated to be stolen). But when I run with the following as the switch...
switch ($obj.Name) {
'SOFM090-107-01-PF-R' { $obj.Notes = "Burn with the rest of the Apple garbage"}
'M094-107-01-PF-R' { $obj.Notes = "Steal immediately!" }
default { $obj.Notes = "Sorry man... I have no idea what that is"}
}
... It sets all the entries to the 'default' setting on the switch. I tried running the switch with a non-hyphenated name for one entry, and a hyphenated entry for the other, and only the hyphenated version was set properly.
The above code is altered from the actual code, but it properly illustrates what I'm trying to do. I need to add a noteproperty that is based off a list of part numbers, and will fill in the "Notes" entry with a tag of my choosing.
I've tried it with single quotes, double quotes, using the -wildcard and replacing the switch hyphens with *'s, and putting the ` character in before the -'s. Nothing seems to be working.
There's nothing wrong with the code, so the problem must lie in the data. I verified that it works fine with a CSV file that has those exact hyphenated values in the "Name" column.
If the switch doesn't work with the hyphenated names, then the values being imported into the Name property don't match what you have in the switch statement. It's a good idea to always post the data you're working with, or a sample of it, because often that's the source of the problem. Even when it isn't, it helps other people understand what you're trying to accomplish and what your code does. Since we don't have the data, I can suggest a few likely possibilities:
You're manually typing the names into the switch statement, and they look like what's in the CSV, but don't actually match, e.g. you're confusing O with 0 because they look the same in the font you're working with. I'd have suspected something like an en-dash instead of a hyphen, but you say you tried replacing the hyphens with wildcards
You have trailing spaces
You're single-quoting the hyphenated names in the CSV file (Import-Csv only understands double quotes; single quotes would be included in the value).
Here are a couple of things you can try to help identify why the data doesn't match (separately, not both together):
Replace switch ($obj.Name) { with switch -regex ($obj.Name) {
Use the following code to show you exactly what PowerShell is seeing in the Name property for each item and which switch conditions are being executed:
Write-Host -NoNewline "[$($obj.Name)] "
switch ($obj.Name) {
'SOFM090-107-01-PF-R' {Write-Host 'burn'; $obj.Notes = "Burn with the rest of the Apple garbage"}
'M094-107-01-PF-R' {Write-Host 'steal'; $obj.Notes = "Steal immediately!"}
default {Write-Host 'sorry'; $obj.Notes = "Sorry man... I have no idea what that is"}
}
If you post the data, we'll probably be able to tell you exactly why it's not working. But I can pretty much guarantee you that if you're using that code, the problem is that the imported Name values that aren't being matched with the right Notes values are in some way not the same as what you have in the switch conditions.