PSMonday #45: March 6, 2017

Topic: Reusable Code III

Notice: This post is a part of the PowerShell Monday series — a group of quick and easy to read mini lessons that briefly cover beginning and intermediate PowerShell topics. As a PowerShell enthusiast, this seemed like a beneficial way to ensure those around me at work were consistently learning new things about Windows PowerShell. At some point, I decided I would share these posts here, as well. Here’s the PowerShell Monday Table of Contents.

Back again.

We’ve made two new changes to the below code. One, we replaced powershell_ise, inside of our Get-Process command, with $Service. Two, we added a new, first line to our code. This command will prompt the user to enter a value. Read-Host will then assign that value to the $Service variable. That value will be used in the remainder of the PowerShell code every place $Service is found (there’s only the one). Now, the code is no longer only good for checking for the powershell_ise process; it’ll check for whatever process is entered.

$Service = Read-Host -Prompt 'Enter a Process Name'
    Get-Process -Name $Service |
        Select-Object -Property @{N='Process Name';E={$_.Name}},
        Description,
        Company,
        @{N='Shared Memory';E={"$([Math]::Round($_.WorkingSet / 1MB)) MB"}},
        @{N='Private Memory Size';
            E={"$([Math]::Round($_.PrivateMemorySize / 1MB)) MB"}}

If you were to open notepad, you could use this code to return its process information. So let’s do that; let’s say we have notepad open and we run our code.

Enter a Process Name:

Enter a Process Name: notepad

Process Name        : notepad
Description         : Notepad
Company             : Microsoft Corporation
Shared Memory       : 9 MB
Private Memory Size : 1 MB

Our few lines of code just became useable against any running process on the computer. To be as thorough as possible, we need to consider what happens when we enter a process that isn’t actually running — what’s it going to do? Let’s close notepad and find out.

Enter a Process Name: notepad

Get-Process : Cannot find a process with the name "notepad". Verify the process name and call the cmdlet again.
At line:3 char:1
+ Get-Process -Name $Service |
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo          : ObjectNotFound: (notepad:String) [Get-Process], ProcessCommandException
+ FullyQualifiedErrorId : NoProcessFoundForGivenName,Microsoft.PowerShell.Commands.GetProcessCommand

Get-Process doesn’t handle this gracefully, so let’s make a few more changes. We’ll first add a try-catch. The try portion will wrap the Get-Process and Select-Object commands. While it isn’t always necessary, we need to add the ErrorAction parameter name, with the Stop parameter value to Get-Process. This is required in order to prevent this error from being displayed. Forcing a terminating error isn’t always required, so don’t add it to commands when it’s not necessary. You try it without -ErrorAction. If it doesn’t work, you try -ErrorAction SilentlyContinue, and if that doesn’t work, you use -ErrorAction Stop, as we’ve done here.

The catch portion of our try-catch wraps a newly added, Write-Warning command that will gracefully indicate when a process isn’t running, or perhaps, just wasn’t spelled correctly.

$Service = Read-Host -Prompt 'Enter a Process Name'

try {
    Get-Process -Name $Service -ErrorAction Stop |
        Select-Object -Property @{N='Process Name';E={$_.Name}},
            Description,
            Company,
            @{N='Shared Memory';E={"$([Math]::Round($_.WorkingSet / 1MB)) MB"}},
            @{N='Private Memory Size';
                E={"$([Math]::Round($_.PrivateMemorySize / 1MB)) MB"}}
} catch {
    Write-Warning -Message "Cannot locate the $Service process."
}


Here’s what happens now, when you enter a process that isn’t running, or doesn’t even exist.

[powershell]
Enter a Process Name: notepad
WARNING: Cannot locate the notepad process.

Enter a Process Name: asdf
WARNING: Cannot locate the asdf process.

That’s it for this Monday. Keep paying attention; it’s about to get good.

Extract Media Folder from PowerPoint Files

On Wednesday, I wrote a response to something on Stack Overflow. So I don’t have to chase it down one day, I though I’d briefly discuss it here and include the updated code I would be more likely to use, providing I ever need to use it.

If you open a PowerPoint .pptx file in something like 7-Zip, you’ll quickly realize that the .pptx is a compressed, or archived, file format. While this brings down the file size (1.65MB file vs. 3.27MB when expanded), this is more more to say that there are a few folders, and several files, that make up a single PowerPoint file.

A person on Stack Overflow wanted to automate the expansion of a series of .pptx files and extract the media folder. This folder holds all the images in a PowerPoint file. I’m not going to bother to explain the code, but let me set the stage. I created a folder on my desktop called pptx. Inside the folder were four PowerPoint files using the .pptx file extension. My code created a new folder for each file in the pptx folder, expanded each PowerPoint file in their own new folders, located the nested media folder, moved it to the top level of their new folder, and then deleted all the other folders and files inside the new folder. If there wasn’t a media folder, it would still create the new folder; however, it would create a single text file in there called “No media folder.txt.” This was to help ensure the user didn’t think the code didn’t work, when they didn’t find the media folder inside the new folder.

I’ll included the original code from the forum post further below, but for now here’s an updated and cleaner version.

$Path = 'C:\users\tommymaynard\Desktop\pptx'
$Files = Get-ChildItem -Path $Path

Foreach ($File in $Files) {
    New-Item -Path $File.DirectoryName -ItemType Directory -Name $File.BaseName | Out-Null
    $NewFolder = $File.FullName.Split('.')[0]

    Add-Type -AssemblyName System.IO.Compression.FileSystem
    [System.IO.Compression.ZipFile]::ExtractToDirectory($File.FullName,$NewFolder)
    
    If (Test-Path -Path "$($NewFolder)\ppt\media") {
        Move-Item -Path "$($NewFolder)\ppt\media" -Destination $NewFolder
        Get-ChildItem -Path $NewFolder | Where-Object {$_.Name -ne 'media'} | Remove-Item -Recurse

    } Else {
        Get-ChildItem -Path $NewFolder | Remove-Item -Recurse
        New-Item -Path $NewFolder -ItemType File -Name 'No media folder.txt' | Out-Null
    }
}

And, here’s the original code and a link to the post itself. This below version didn’t use the $NewFolder variable, so there’s overwhelming inclusion of this piece of code: $File.FullName.Split(‘.’)[0]. It also defaulted to use the Expand-Archive cmdlet, if that was available. As I mentioned in the Stack Overflow post itself, it’s so slow. I’ve removed that so that it’s much faster now, which reminded me, I’ve actually written about this time difference before. It was just last October.

$Path = 'C:\users\tommymaynard\Desktop\pptx'
$Files = Get-ChildItem -Path $Path

Foreach ($File in $Files) {
    New-Item -Path $File.DirectoryName -ItemType Directory -Name $File.BaseName | Out-Null

    If (Get-Command -Name Expand-Archive) {
        Expand-Archive -Path $File.FullName -OutputPath $File.FullName.Split('.')[0]

    } Else {
        Add-Type -AssemblyName System.IO.Compression.FileSystem
        [System.IO.Compression.ZipFile]::ExtractToDirectory($File.FullName,$File.FullName.Split('.')[0])
    } # End If-Else.

    If (Test-Path -Path "$($File.FullName.Split('.')[0])\ppt\media") {
        Move-Item -Path "$($File.FullName.Split('.')[0])\ppt\media" -Destination $File.FullName.Split('.')[0]
        Get-ChildItem -Path $File.FullName.Split('.')[0] | Where-Object {$_.Name -ne 'media'} | Remove-Item -Recurse

    } Else {
        Get-ChildItem -Path $File.FullName.Split('.')[0] | Remove-Item -Recurse
        New-Item -Path $File.FullName.Split('.')[0] -ItemType File -Name 'No media folder.txt' | Out-Null
    } # End If-Else.
} # End Foreach.

Modify the Time Zone Without tzutil.exe

Note: This post has been updated below (3/9/2017).

Earlier today (2/27/2017), I wanted a purely PowerShell answer to setting the time zone. That meant that I didn’t want to rely on tzutil.exe, as I had forever now. The best I could do was to use PowerShell with the registry, in order to update the TimeZoneKeyName key. So I can refer to the code, as I’ll forget it soon enough, I thought I’d place it here with the possibility it’ll also be helpful for someone else, someday.

I do want to mention that this method would require elevation to a local administrator, as would any modification to HKLM. Using tzutil.exe, however, does not include this same restriction. On that alone, I may actually stick with the Windows Time Zone (command line) Utility.

$RegistryPath = 'HKLM:\SYSTEM\CurrentControlSet\Control\TimeZoneInformation'
$RegistryKeyName = 'TimeZoneKeyName'
$RegistryValue = 'US Mountain Standard Time'
Set-ItemProperty -Path $RegistryPath -Name $RegistryKeyName -Value $RegistryValue


Update
: I saw a recent Tweet from Jeffery Hicks about the newer, PowerShell 5.1 function, Get-ComputerInfo. I ran it, as I have at least once before, and ended up staring right at a TimeZone property. So with that, I jumped back to this post to do an update. I think it’s coming…

PSMonday #44: February 27, 2017

Topic: Reusable Code II

Notice: This post is a part of the PowerShell Monday series — a group of quick and easy to read mini lessons that briefly cover beginning and intermediate PowerShell topics. As a PowerShell enthusiast, this seemed like a beneficial way to ensure those around me at work were consistently learning new things about Windows PowerShell. At some point, I decided I would share these posts here, as well. Here’s the PowerShell Monday Table of Contents.

In today’s PSMonday, we’ll jump right back into calculated properties, as we continue to create some reusable code. Last week our final addition was to use a calculated property to modify the name of a property, as can be seen below. We took its default value of “Name,” and changed it to “Process Name.”

Get-Process -Name powershell_ise |
    Select-Object -Property @{Name='Process Name';Expression={$_.Name}},Description,Company,WorkingSet,PrivateMemorySize

Today we’ll add two more calculated properties, but before we do, let’s reformat this code a bit, so it fits better. First, we’re going to use “N” instead of Name and “E” instead of Expression, for the keys in our calculated property hash table. Second, we’ll take advantage of the commas between our  properties which allows each of them to sit on their own line. We could have put a space after each comma and left them on the same line as the first property, and it would have wrapped them for us; however, we’re about to add those other calculated properties.

Get-Process -Name powershell_ise |
    Select-Object -Property @{N='Process Name';E={$_.Name}},
        Description,
        Company,
        WorkingSet,
        PrivateMemorySize

The WorkingSet and PrivateMemorySize values are being reported in bytes. Let’s modify that, beginning with the WorkingSet property. Take a look at the newest changes, and we’ll discuss them after the example and its results.

Get-Process -Name powershell_ise |
    Select-Object -Property @{N='Process Name';E={$_.Name}},
        Description,
         Company,
        @{N='Shared Memory';E={"$([Math]::Round($_.WorkingSet / 1MB)) MB"}},
        PrivateMemorySize

Process Name      : powershell_ise
Description       : Windows PowerShell ISE
Company           : Microsoft Corporation
Shared Memory     : 420 MB
PrivateMemorySize : 405536768

As you can see in the above example, we first changed the term “WorkingSet” to “Shared Memory.” This time, however, we also modify the value it displays using the Expression key-value pair. We won’t go into this too deep, other than to say we first, divided our Working Set value by 1 MB, as we wanted to report our values in megabytes. Second, we directly accessed .NET to round our value using a Round method, and finally, we added the string “MB” to the end of the value to make it clear what measurement we’re using.

Next, we’ll add a third calculated property, but this time to the PrivateMemorySize property. This will allow it to function just like the modified WorkingSet property. As you can likely tell, I’ve used more lines than is necessary, in order that this best fits.

Get-Process -Name powershell_ise |
    Select-Object -Property @{N='Process Name';E={$_.Name}},
        Description,
        Company,
        @{N='Shared Memory';E={"$([Math]::Round($_.WorkingSet / 1MB)) MB"}},
        @{N='Private Memory Size';
            E={"$([Math]::Round($_.PrivateMemorySize / 1MB)) MB"}}

Process Name        : powershell_ise
Description         : Windows PowerShell ISE
Company             : Microsoft Corporation
Shared Memory       : 420 MB
Private Memory Size : 387 MB

Okay, let’s stop here and pick up from this point next Monday.

PSMonday #43: February 20, 2017

Topic: Reusable Code I

Notice: This post is a part of the PowerShell Monday series — a group of quick and easy to read mini lessons that briefly cover beginning and intermediate PowerShell topics. As a PowerShell enthusiast, this seemed like a beneficial way to ensure those around me at work were consistently learning new things about Windows PowerShell. At some point, I decided I would share these posts here, as well. Here’s the PowerShell Monday Table of Contents.

After I type the same thing a few times, and suspect I’ll need, or want, to continue to do that, I’ll typically write a function for it, so that I never have to type it again. That’s the purpose of a function. It’s a container for a series of commands that I can run, or invoke, simply by entering its name. Today we’ll start a four-part PSMonday on how to reuse code from beginning to end. As we close up PSMonday, these last several are going to be the ones in which to really pay attention.

Let’s start by entering a simple, Get-Process command, in order to determine whether or not the PowerShell ISE — the PowerShell Integrated Scripting Environment — is currently running.

Get-Process -Name powershell_ise | Select-Object -Property ProcessName,Id

ProcessName      Id
-----------      --
powershell_ise 5732

In the above example, we used Select-Object to filter the properties that are returned by Get-Process. The ISE is running, otherwise we would’ve received an error, indicating that the process could not be found.

Next, let’s pipe the results of an unfiltered, Get-Process command to a mildly modified Get-Member command, and take a look at all the properties of the returned object.

Get-Process -Name powershell_ise | Get-Member -MemberType Property

Here’s another filtered Get-Process command, that only includes the properties that hold information in which I am interested.

Get-Process -Name powershell_ise |
    Select-Object -Property Name,Description,Company,WorkingSet,PrivateMemorySize

Name              : powershell_ise
Description       : Windows PowerShell ISE
Company           : Microsoft Corporation
WorkingSet        : 162627584
PrivateMemorySize : 153522176

Let’s make a change to the Name property using what’s called a calculated property. Among other things, a calculated property allows us to rename a property’s default name to something else.

Get-Process -Name powershell_ise |
    Select-Object -Property @{Name='Process Name';Expression={$_.Name}},Description,Company,WorkingSet,PrivateMemorySize

Process Name      : powershell_ise
Description       : Windows PowerShell ISE
Company           : Microsoft Corporation
WorkingSet        : 162324480
PrivateMemorySize : 153812992

The calculated property looks like this, when it’s all by itself.

@{Name='Process Name';Expression={$_.Name}}

Notice in the above results that Name property is now named, Process Name. The calculated property consists of a hash table, as signified by @{}, that contains two, key-value pairs. One pair includes the Name key, and the second key-value pair includes an Expression key. We’ll continue next week with some additional examples of calculated properties, as we continue to learn about creating reusable code.

PSMonday #42: February 13, 2017

Topic: While

Notice: This post is a part of the PowerShell Monday series — a group of quick and easy to read mini lessons that briefly cover beginning and intermediate PowerShell topics. As a PowerShell enthusiast, this seemed like a beneficial way to ensure those around me at work were consistently learning new things about Windows PowerShell. At some point, I decided I would share these posts here, as well. Here’s the PowerShell Monday Table of Contents.

The last language construct we’re going to discuss is the While loop — yes, another looping construct. Let’s start with the help file in the first below command, followed by two ways to write the conceptual structure of the While loop.

Get-Help -Name about_While -ShowWindow

While (<condition>){<statement list>}

While (<condition>) {
     <statement list>
}

Our first below example of the While loop, well, it does absolutely nothing. We begin by setting our $Value variable to the numeric value of 1, and then we (attempt) to enter the While loop. The biggest difference between the While loop and the Do loop is that our conditions come before the statement lists. This should be reminiscent of the If, and Switch statement variations, where the evaluations come first.

$Value = 1

While ($Value -eq 5) {
    $Value
    $Value = $Value + 1
}

In the next example, we’ll make one simple change and the code will run. Our condition will evaluate the $Value variable such that it’s $true when $Value is less than or equal to 5. Remember from the For loop, that $Value++ is the same as writing $Value = $Value + 1.

$Value = 1

While ($Value -le 5) {
    $Value
    $Value++
}

1
2
3
4
5

Our final example for the While loop, gives the user an opportunity to try and guess a number between one and five. Before we potentially, enter the While construct, we’ll set the $MagicNumber variable to the random number and the $Continue variable to $true. We enter the While providing that $Continue is equal to $true, and we know it will be, as it was just assigned that value.

Inside the statement list, we (1) request the user enter a number between one and five, and (2) use and If-Else to see if the $MagicNumber matches the guess entered by the user, as stored in $Guess. If it matches, we indicate that, and then set $Continue to $false. This means that the next time the While begins, the condition won’t match and we won’t enter the While. The loop will be over. If $Guess and $MagicNumber don’t match, the While will execute again, and we’ll ask for another number.

$MagicNumber = Get-Random -Minimum 1 -Maximum 5
$Continue = $true

While ($Continue -eq $true) {
    $Guess = Read-Host "Enter the magic number"
    If ($Guess -eq $MagicNumber) {
        "You guessed it: $Guess <--> $MagicNumber"
        $Continue = $false

    } Else {
        Write-Output -InputObject 'Try again.'
    }
}

Enter the magic number: 5
Try again.
Enter the magic number: 4
Try again.
Enter the magic number: 2
Try again.
Enter the magic number: 3
You guessed it: 3 <--> 3

And, that’s it. We’ve seen and learned everything from If to Switch and For to While. As it was stated previously, knowing which language construct to use should be left to your intuition, and a full understanding of a language’s conditional options is the only way that’s going to happen. Next week we’ll start discussing reusable code.

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

Three Ways to Set $PSDefaultParameterValues

Update: When you’re done here, read Part II.

Although we’ve discussed the $PSDefaultParameterValues before, I wanted to do a quick recap. I need one place that shows the various ways to set this variable. That’s what this post will do for me, and perhaps you too.

First, however, let’s remind everyone what the $PSDefaultParameterValues variable does for us. It allows us to set a custom, default value for a function or cmdlet’s parameter. One of the examples I mentioned before, in one of the three posts I’ve written about $PSDefaultParameterValues (1 | 2 | 3), used Get-Help.

This cmdlet includes a ShowWindow switch parameter that will open the full help inside its own GUI window. I tend to use this option a great deal to keep my ConsoleHost clean. In order to keep this post short, I’m just going to write the three ways in which I’m aware that we can set this variable.

$PSDefaultParameterValues.Add('Get-Help:ShowWindow',$true)

$PSDefaultParameterValues = @{'Get-Help:ShowWindow' = $true}

$PSDefaultParameterValues['Get-Help:ShowWindow'] = $true

Oh, Lee Holmes posted a welcome PSDefaultParameterValues addition on Twitter recently. I’ve included that addition below using the three above options, as well. Unlike his example, I moved from three underscores to two. You’ll see what I mean below if you haven’t already read the Tweet.

With this example in place, the results of all the commands entered will end up in the $__ variable. Again, that’s two underscores. Run a Get-ADUser command, for instance, and you’ll get the results both on the screen and in the $__ variable up until you run another command that can make use of the OutVariable common parameter.

$PSDefaultParameterValues.Add('Out-Default:OutVariable','__')

$PSDefaultParameterValues = @{'Out-Default:OutVariable' = '__'}

$PSDefaultParameterValues['Out-Default:OutVariable'] = '__'

ConsoleHost to ConsoleHost Variable

I often do things, or learn things, in PowerShell that lead me in the direction of writing a new post. Sometimes it’s just experimenting, and sometimes it’s a part of a bigger project. Well, the latter is why we are here today. A current profile script project of mine — that I’ll post as soon as possible — requires that a new ConsoleHost (that’s the standard blue console screen; not the ISE), open and then the current one close. That part is easy.

The problem is that my new ConsoleHost needs to know if a variable has been set, or not, in the previous ConsoleHost. Here, follow along. Start by opening a new ConsoleHost. Inside that host program, create a variable and assign it some sort of value, as I’ve done in the below example.

PS > $Variable = 'This is my standard variable.'
PS > $Variable
This is my standard variable.

In my above example, I tested that my variable had been properly assigned by returning its value. So far, so good. Now, we’ll run a command to (1) open a ConsoleHost from this ConsoleHost, and (2) exit the original ConsoleHost. The semi-colon is a command separator. It allows me to put two commands on the same line and have them run in succession with a single press of the Enter key. One ConsoleHost closes and a new one opens.

PS > Start-Process -FilePath powershell.exe; exit

Now that we have a new ConsoleHost and our old one has exited, let’s see if our variable is waiting for us.

PS > $Variable
PS > 

Nope. That variable was created in a different PowerShell session, and so there’s no getting it back. It simply doesn’t exist any longer. Without fully thinking it through, I briefly thought to use a globally scoped variable instead. It wasn’t long at all, before I realized what a foolish idea that had been. My ConsoleHost was the global scope. With another ConsoleHost, all I’d have is another, separate global scope. Not helpful. I considered writing the variable to disk, but luckily, I didn’t have to consider that option for long either. I had a better idea; I was going to use an environmental variable. Follow along.

PS > $env:Variable = 'This is my standard, environmental variable.'
PS > $env:Variable
This is my standard, environmental variable.
PS > Start-Process -FilePath powershell.exe; exit
Windows PowerShell
Copyright (C) 2016 Microsoft Corporation. All rights reserved.

PS > # This is a new PowerShell ConsoleHost.
PS > $env:Variable
This is my standard, environmental variable.
PS > ise
PS > # This is in the PowerShell ISE Host.
PS > $env:Variable
This is my standard, environmental variable.

So, as we’ve seen, the environmental variable will follow us along from ConsoleHost to ConsoleHost to the ISE too, providing we open each of them from an existing host that includes the environment variable. Oh, and if you were wondering, the variable is still with us if we launch a ConsoleHost from the ISE (there’s a button for that). The environmental variable option works beautifully for my upcoming profile script project. I’ll link that from here when it’s up, and you’ll already understand that piece of it!

So it’s been included, the environmental variables are presented as a drive by PowerShell. The following command will show all of these variables. If you see some in there that you can use, then do so. It’s always better to use $env:COMPUTERNAME, than it is to hard code a computer name inside your functions.

Get-ChildItem -Path env:

Name                           Value
----                           -----
ALLUSERSPROFILE                C:\ProgramData
APPDATA                        C:\Users\tommymaynard\AppData\Roaming
CommonProgramFiles             C:\Program Files\Common Files
CommonProgramFiles(x86)        C:\Program Files (x86)\Common Files
CommonProgramW6432             C:\Program Files\Common Files
...
USERNAME                       tommymaynard
USERPROFILE                    C:\Users\tommymaynard
Variable                       This is my standard, environmental variable.
windir                         C:\Windows

Okay, now we’re really done with today’s post, and oh, hey look, there’s our $env:Variable variable again.

PSMonday #41: February 6, 2017

Topic: Do-Until

Notice: This post is a part of the PowerShell Monday series — a group of quick and easy to read mini lessons that briefly cover beginning and intermediate PowerShell topics. As a PowerShell enthusiast, this seemed like a beneficial way to ensure those around me at work were consistently learning new things about Windows PowerShell. At some point, I decided I would share these posts here, as well. Here’s the PowerShell Monday Table of Contents.

I said I would say it this week too, so let’s get it out of the way: The Do-While and Do-Until loops ensure that the statement list is run at least once. Your code is going to execute at least one time using a Do construct. This is because the condition, that determines whether or not we loop again, isn’t evaluated until the statement list has run once. If you need to ensure your code executes at least one time, then the Do-While and Do-Until variations of the Do loop, may be the language construct you need.

Here’s the conceptual examples of the Do-Until.

Do {<statement list>} Until (<condition>)

Do {
    <statement list>

} Until (<condition>)

Our first below example should look somewhat familiar. We saw it first last week in the Do-While examples. We assign 1 to the $Number variable and then enter the Do loop. We output a string statement and then increment the $Number variable. Then, we check the condition. The condition indicates that we’ll continue to loop until $Number is less than 10. Well, 1 is always less than 10, so we exit the construct. This is a clear example that we always run the statement list at least once in a Do loop, before we check the condition.

$Number = 1

Do {
    Write-Output -InputObject "In the Do loop ($Number)."
    $Number++

} Until ($Number -lt 10)

In the Do loop (1).

Let’s modify this example. We’ll reinitialize the $Number variable back to 1 and start again. This time our condition states that we’ll continue to loop until $Number is greater than 10. As $Number is lower, you can likely guess that we’re going to run through this loop several times. Ten times, to be exact.

$Number = 1

Do {
    Write-Output -InputObject "In the Do loop ($Number)."
    $Number++

} Until ($Number -gt 10)

In the Do loop (1).
In the Do loop (2).
In the Do loop (3).
In the Do loop (4).
In the Do loop (5).
In the Do loop (6).
In the Do loop (7).
In the Do loop (8).
In the Do loop (9).
In the Do loop (10).

In closing out the Do variations, we’ll add a final example. Much like last week, we’ll transition from using numeric values to date times. In this example, we’ll add 10 seconds to the current time. Then, we’ll output a string value and sleep (pause) for a second. Eventually, the current time will be greater than the time when we began; therefore, the condition will be $true and we’ll exit the looping construct.

$Time = (Get-Date).AddSeconds(10)

Do {
    Write-Output -InputObject 'Waiting for time to pass...'
    Start-Sleep -Seconds 1

} Until ((Get-Date) -ge $Time)

Waiting for time to pass...
Waiting for time to pass...
Waiting for time to pass...
Waiting for time to pass...
Waiting for time to pass...
Waiting for time to pass...
Waiting for time to pass...
Waiting for time to pass...
Waiting for time to pass...
Waiting for time to pass...

Next week is our final language construct — the While loop. By then, you’ll have a full and complete understanding of the various control structures we can use to respond to conditions in PowerShell. In case you hadn’t considered it, learning these concepts — even if it’s with PowerShell — is going to make learning other scripting, and even programming languages, easier to do.