Tag Archives: Switch

Forcing a Switch Parameter to be False

The coworker that sits next to me at the office has upped his PowerShell game tremendously since he’s moved up from Operations to the Windows Team. He often has well-thought-out questions and he’s long shown a great desire to learn about PowerShell. It’s addictive and rewarding, so there’s no question as to why. He may not know it, but I look forward to the questions. The need for PowerShell answers here and there keeps me fresh, and so while it benefits him, it benefits me too.

As he does, he asked a question recently. It was in regard to the Confirm parameter. Why does it need the colon and $false to not be true? Why does it need to be written like this: -Confirm:$false? Why can’t it be written like this: -Confirm $false?

If a switch parameter is included, then its value is $true. If it’s not included, its value is $false. Knowing that alone, makes it clear that we’d have to force something to be false if its default is $true. Okay, knowing that, why can’t we do this then: -Confirm $false?

It’s got to do with the PowerShell parser. It’s how the engine evaluates the statement. Remember, it’s a switch parameter. The parameter is either included or it’s not. The switch parameter is inherently never dependent on a value to be included with it. It doesn’t work that way. The PowerShell engine will see -Confirm and make it $true, long before it even sees the $false value. The colon and value being attached to the Confirm parameter ensures the parser knows you want it to be of that specific value.

Sync Profile Script from Work to Home

After a few years of this whole I-do-PowerShell-everyday thing, my profile script has become quite the necessity at both work, and home. In fact, there’s much in my work profile script that I want at home, such as my prompt function, among others. With this ever present need, I set off to create a profile script that self-updates between my work and home computer, without any continuing need for me to do it myself.

So, about the profile script, just in case you ended up here and are clueless to that in which I’m referring. There’s a potential .ps1 file that can be created and edited so that every time you open the PowerShell ConsoleHost things run in the background, as the session begins. I use it to initialize variables, to create aliases, to declare functions, and more. In regard to Profiles, here’s an old Scripting Guy article that may be of help if you need it. I may have started there myself a few years ago.

I’ve broken the structure of my profile script template into three logical parts. Have a look at the first section of the template below, and then we’ll discuss it.

$WorkComputer = 'WorkComputer'
$HomeComputer = 'HomeComputer'

Switch ($env:COMPUTERNAME) {

    # Work computer only.
    {$_ -eq $WorkComputer} {

    } # End work computer only.

    # Home computer only.
    {$_ -eq $HomeComputer} {

    } # End home computer only.

    # Work and home computer.
    {$_ -eq $WorkComputer -or $_ -eq $HomeComputer} {
 
    } # End Work and home computer.
} # End Switch.

In lines 1 and 2, we create two variables, $WorkComputer and $HomeComputer. As you might expect, these variables store the computer names of my work, and my home computers. If you choose to use this template, then these variable assignments will require a small bit of manual editing on your own; however, it’s a one time thing, and in my mind, a small price to pay to have your profile synced between two computers.

Following these two variable assignments, is the above Switch statement. This defines what will be available on the two different computers. The first section in the Switch are things I only want available on my work computer, the second section are things I only want available on my home computer only, and the last section is for things that I want available on both my work and home computer. This last section is where I placed my prompt function, for instance, in order that it’s available regardless of where I’m working. I really don’t want to be without it.

At the end of this post, I’ll included the full, uninterrupted code, but for now, let’s have a look at the next section. This section defines the Sync-ProfileScript function.

# Create Sync profile function.
Function Sync-ProfileScript {
    [CmdletBinding()]
    Param (
        [Parameter(Mandatory=$true)]
        [ValidateSet('Source','Destination')]
        [string]$ComputerType,

        [Parameter()]
        [string]$LocalProfileScriptPath = "$env:USERPROFILE\Dropbox\PowerShell\Profile\Microsoft.PowerShell_profile.ps1"
    )

    Begin {
        # Exit if NOT the ConsoleHost.
        If (-Not($Host.Name -eq 'ConsoleHost')) {
            break
        }
    } # End Begin.

    Process {
        If (Test-Path -Path $PROFILE) {
            $CompareFiles = Compare-Object -ReferenceObject (Get-Item $PROFILE).LastWriteTime -DifferenceObject (Get-Item $LocalProfileScriptPath).LastWriteTime -IncludeEqual |
                Select-Object -First 1

            If ([System.Boolean]($CompareFiles.SideIndicator -ne '==')) {

                Switch ($ComputerType) {
                    'Source' {Copy-Item -Path $PROFILE -Destination $LocalProfileScriptPath}

                    'Destination' {
                        Copy-Item -Path $LocalProfileScriptPath -Destination $PROFILE

                        'Profile Script has been updated. Restart ConsoleHost?'
                        Do {
                            $Prompt = Read-Host -Prompt 'Enter r to Restart or c to Cancel'
                        } Until ($Prompt -eq 'r' -or $Prompt -eq 'c')

                        If ($Prompt -eq 'r') {
                            Start-Process -FilePath powershell.exe
                            Stop-Process -Id $PID
                        }
                    }
                } # End Switch.
            } # End If.
        } # End If.
    } # End Process.

    End {
    } # End End.
} # End Function: Sync-ProfileScript.

The purpose of the Sync-ProfileScript function is to copy the profile script to a Dropbox folder, when the ConsoleHost is opened on the work computer. Additionally, it serves to copy the profile script from that same Dropbox folder, when the ConsoleHost is opened on the home computer. Keep in mind that I’ve purposely written this function to always go from the work computer, to the home computer. I don’t plan to make changes in the profile script on the home computer, that I’ll then expect or want, on the work computer.

Let’s cover what’s happening in this function. In lines 1 – 11 we define out Sync-ProfileScript function with two parameters: ComputerType and LocalProfileScriptPath. We use the ComputerType parameter to indicate whether this is the source or destination computer. Source is work and destination is home. The LocalProfileScriptPath parameter points to the profile script (.ps1 file), located in Dropbox. As it has a default value, I don’t have to send in a parameter value when the function is invoked.

Our Begin block serves one quick purpose and that’s to exit the function immediately, if we’re not in the ConsoleHost, and instead inside a host such as the ISE. The magic happens in the Process block. Here’s what we do, in order: (1) Test to see that there’s an actual profile script being used, (2) if there is, compare the LastWriteTime on the profile script file used by the ConsoleHost ($PROFILE), and the file in Dropbox, (3) if they are different, either copy the profile script used by the ConsoleHost to Dropbox, or copy the profile script in Dropbox to the location used by the ConsoleHost. Again, this is dependent on which computer we’re using: work or home.

Let’s stop and consider something from the perspective of the home computer. If I open the PowerShell ConsoleHost there, it’ll potentially download the newest version of the profile script from Dropbox to its place on the filesystem. Great. The problem is that any changes to the profile script won’t be usable until the next time that ConsoleHost is started. I think I could’ve run & $PROFILE, but I skipped that option as I vaguely remember that it didn’t always work for me.

Therefore, I added a bit more code. If the home computer notices a change to the profile script, it’ll indicate that to the user by writing “Profile Script has been updated. Restart ConsoleHost?” and “Enter r to Restart or c to Cancel.” If the user enters “r,” it’ll restart the ConsoleHost loading the newest version of the profile script by default. If the user enters “c,” it’ll cancel the ConsoleHost restart and the newest version of the profile script will not be updated on the home computer. It will, however, be updated the next time a ConsoleHost is opened.

Now, on to the final portion of my profile script. Don’t worry, this part is less involved than the Sync-ProfileScript function.

# Determine if sync counter variable exists.
If (-Not($env:SyncProfileCounter)) {
    $env:SyncProfileCounter = 0

    # Copy profile script to/from Dropbox by calling Sync-ProfileScript.
    If ($env:COMPUTERNAME -eq $WorkComputer) {
        Sync-ProfileScript -ComputerType Source

    } ElseIf ($env:COMPUTERNAME -eq $HomeComputer) {
        Sync-ProfileScript -ComputerType Destination
    } # End If-ElseIf.
} # End If.

This section of the profile script invokes the Sync-ProfileScript function we discussed in the last section. If it’s run on the work computer, it indicates to the Sync-ProfileScript function to copy the profile script to Dropbox. If it’s run on the home computer, it indicates to the function to copy the profile script from Dropbox. We know this already, however. It uses a counter variable stored inside an environmental variable to ensure this If statement doesn’t perpetually run, and therefore perpetually call the Sync-ProfileScript function. I won’t a separate post about that here.

I realize that not everyone is using Dropbox. If you’re using another service, then I highly suspect you can use what you’ve learned here, with them as well. You’ll just need to determine the local path to use, and adjust your profile script accordingly. If someone wants to let me know about OneDrive, that would be great. I’d be more than happy to include that information in this post!

Here’s the complete profile script template. Thanks for your time.

$WorkComputer = 'WorkComputer'
$HomeComputer = 'HomeComputer'

Switch ($env:COMPUTERNAME) {

    # Work computer only.
    {$_ -eq $WorkComputer} {

    } # End work computer only.

    # Home computer only.
    {$_ -eq $HomeComputer} {

    } # End home computer only.

    # Work and home computer.
    {$_ -eq $WorkComputer -or $_ -eq $HomeComputer} {
 
    } # End Work and home computer.
} # End Switch.

# Create Sync profile function.
Function Sync-ProfileScript {
    [CmdletBinding()]
    Param (
        [Parameter(Mandatory=$true)]
        [ValidateSet('Source','Destination')]
        [string]$ComputerType,

        [Parameter()]
        [string]$LocalProfileScriptPath = "$env:USERPROFILE\Dropbox\PowerShell\Profile\Microsoft.PowerShell_profile.ps1"
    )

    Begin {
        # Exit if NOT the ConsoleHost.
        If (-Not($Host.Name -eq 'ConsoleHost')) {
            break
        }
    } # End Begin.

    Process {
        If (Test-Path -Path $PROFILE) {
            $CompareFiles = Compare-Object -ReferenceObject (Get-Item $PROFILE).LastWriteTime -DifferenceObject (Get-Item $LocalProfileScriptPath).LastWriteTime -IncludeEqual |
                Select-Object -First 1

            If ([System.Boolean]($CompareFiles.SideIndicator -ne '==')) {

                Switch ($ComputerType) {
                    'Source' {Copy-Item -Path $PROFILE -Destination $LocalProfileScriptPath}

                    'Destination' {
                        Copy-Item -Path $LocalProfileScriptPath -Destination $PROFILE

                        'Profile Script has been updated. Restart ConsoleHost?'
                        Do {
                            $Prompt = Read-Host -Prompt 'Enter r to Restart or c to Cancel'
                        } Until ($Prompt -eq 'r' -or $Prompt -eq 'c')

                        If ($Prompt -eq 'r') {
                            Start-Process -FilePath powershell.exe
                            Stop-Process -Id $PID
                        }
                    }
                } # End Switch.
            } # End If.
        } # End If.
    } # End Process.

    End {
    } # End End.
} # End Function: Sync-ProfileScript.

# Determine if sync counter variable exists.
If (-Not($env:SyncProfileCounter)) {
    $env:SyncProfileCounter = 0

    # Copy profile script to/from Dropbox by calling Sync-ProfileScript.
    If ($env:COMPUTERNAME -eq $WorkComputer) {
        Sync-ProfileScript -ComputerType Source

    } ElseIf ($env:COMPUTERNAME -eq $HomeComputer) {
        Sync-ProfileScript -ComputerType Destination
    } # End If-ElseIf.
} # End If.

TechNet Wiki Link:
https://social.technet.microsoft.com/wiki/contents/articles/37104.powershell-sync-profile-script-from-work-to-home.aspx

February 2017 Guru Link:
https://social.technet.microsoft.com/wiki/contents/articles/36936.technet-guru-competitions-february-2017.aspx

PowerShell Nested Switch Statements

There was a Reddit post recently that gave me an opportunity to try something I’m not sure I’ve tried before: nested switch statements — switch statements inside switch statements. So we’re on the same page, I’ve written an example of a basic, non-nested switch. It provides some of the same functionality as an If-ElseIf. I’ll typically use a switch statement instead of an If-ElseIf when there’s three or more outcomes. It’s easier to read, and it looks cleaner.

In the example below, a specific phrase will be written if the value in the $Fruit variable is apple, banana, or pear. If it’s not one of those, it’ll take the default action. In the case below, the default action indicates that an apple, banana, or pear wasn’t chosen. Switch statements¬†will sometimes be called case statements in other languages.

$Fruit = 'banana'
Switch ($Fruit) {
    'apple' {'You chose apple.'; break}
    'banana' {'You chose banana.'; break}
    'pear' {'You chose pear.'; break}
    default {'You didn''t choose an apple, banana, or pear.'}
}

As a part of my nested switch statement testing, I wrote this example using automobile makes and models. The outermost switch statement makes a determination of which nested switch statement to execute.

$Make = 'Ford'
$Model = 'Escape'
 
Switch ($Make) {
    'Ford' {
        Switch ($Model) {
            'Mustang' {Write-Output -InputObject 'You selected the Ford Mustang.'; break}
            'Escape' {Write-Output -InputObject 'You selected the Ford Escape.'; break}
            default {Write-Output -InputObject "You selected Ford, however, the $Model isn't a valid model for this make."; break}
        }
    }
    'Chevy' {
        Switch ($Model) {
            'Corvette' {Write-Output -InputObject 'You selected the Chevy Corvette.'; break}
            'Camaro' {Write-Output -InputObject 'You selected the Chevy Camaro.'; break}
            default {Write-Output -InputObject "You selected Chevy, however, the $Model isn't a valid model for this make."; break}
        }
    }
    'Dodge' {
        Switch ($Model) {
            'Charger' {Write-Output -InputObject 'You selected the Dodge Charger.'; break}
            'Viper' {Write-Output -InputObject 'You selected the Dodge Viper.'; break}
            default {Write-Output -InputObject "You selected Dodge, however, the $Model isn't a valid model for this make."; break}
        }
    }
}

Should we take this further — a switch statement inside a switch statement inside a switch statement? I’m not going to lie, it started to get confusing and so my first recommendation would be to try and steer clear of the multi-nesting this deep (3 levels). I’ve removed some of the example above — Chevy and Dodge — in my example below and focused on the Ford (pun, absolutely intended). To help, I’ve included comments on the closing brackets. This multi-nested switch statement will return different values based on the make, the model, and the year, too.

$Make = 'Ford'
$Model = 'Escape'
$Year = '2016'
  
Switch ($Make) {
    'Ford' {
        Switch ($Model) {
            'Mustang' {
                Switch ($Year) {
                    '2015' {Write-Output -InputObject "You selected the 2015 Ford Mustang."; break}
                    '2016' {Write-Output -InputObject "You selected the 2016 Ford Mustang"; break}
                    default {Write-Output -InputObject "You selected the Ford Mustang with a non-matching year."; break}
                } # End Mustang Year Switch.
            } # End Mustang Switch.
            'Escape' {
                Switch ($Year) {
                    '2015' {Write-Output -InputObject "You selected the 2015 Ford Escape."; break}
                    '2016' {Write-Output -InputObject "You selected the 2016 Ford Escape."; break}
                    default {Write-Output -InputObject "You selected the Ford Escape with a non-matching year."; break}
                } # End Escape Year Switch.
            } # End Escape Switch.
            default {Write-Output -InputObject "You selected a Ford; however, the $Model isn't a valid model for this make."}
        } # End Ford Model Switch.
    } # End Ford Switch.
    default {Write-Output -InputObject 'Sorry, we''re only dealing with Fords (at the moment).'}
} # End Make Switch.

So I can track it down later, here’s the post I read on Reddit that influenced this post. It includes the link to my possible, partial solution; however, that direct link is here: http://pastebin.com/KRxRb1VM.

Update: It has occurred to me that it might be easier to follow this thrice nested switch without all the comments. I’ve removed those below.

$Make = 'Ford'
$Model = 'Escape'
$Year = '2016'
  
Switch ($Make) {
    'Ford' {
        Switch ($Model) {
            'Mustang' {
                Switch ($Year) {
                    '2015' {Write-Output -InputObject "You selected the 2015 Ford Mustang."; break}
                    '2016' {Write-Output -InputObject "You selected the 2016 Ford Mustang"; break}
                    default {Write-Output -InputObject "You selected the Ford Mustang with a non-matching year."; break}
                }
            }
            'Escape' {
                Switch ($Year) {
                    '2015' {Write-Output -InputObject "You selected the 2015 Ford Escape."; break}
                    '2016' {Write-Output -InputObject "You selected the 2016 Ford Escape."; break}
                    default {Write-Output -InputObject "You selected the Ford Escape with a non-matching year."; break}
                }
            }
            default {Write-Output -InputObject "You selected a Ford; however, the $Model isn't a valid model for this make."}
        }
    }
    default {Write-Output -InputObject 'Sorry, we''re only dealing with Fords (at the moment).'}
}