Tag Archives: Dropbox

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

Save External Dynamic IP to Dropbox

There are times when I’m away from the house, where I need to be able to reach my home computer via Remote Desktop. Because of this occasional need, I have my home router set up to allow this external connection. To do this required a static (internal) IP assignment for my home computer (I actually use a DHCP reservation), my router listening on port 3389 on the outside IP, and port forwarding that routes this external traffic to the home computer’s internal IP address. I used to connect by name, but the application I was using to sync my IP with my hostname, doesn’t seem to be working consistently. Therefore, I’ve been using my fallback option a lot lately: PowerShell and Dropbox.

Here’s what happens: I have a scheduled task on my home computer that runs a PowerShell script at 12 a.m., 6 a.m., 12 p.m, and 6 p.m. each day. The purpose of the script is to get my current, outside IP and write it, as well as the date and time, to a text file in Dropbox. That file is then snyced to my other computer and phone. It’s simple.

In line one, below, we create a variable, $UseableDate, to hold the string I use for the date and time. For today’s date, it might look something like this: D2015-08-19_T09-02-43-PM. This has long been my preferred way to store the date and time, especially when used in file names, as it will keep everything in a sort able order by year, month, and then day, and doesn’t include any invalid characters when used in a file path.

$UseableDate = Get-Date -Format 'Dyyyy-MM-dd_Thh-mm-ss-tt'

Next we need to collect the outside IP address. This is an important distinction — I don’t want my internal, NAT’d IP address. Line two, below, which checks in with dyndns.com, was borrowed from Aman Dhally. I’ve included a couple other options that can be used to populate the same $IPAddress variable (note: ifconfig.me has always seemed to be slower than the rest). Notice that using something other than dyndns will not require using any Regex, just some trimming to remove some white space surrounding the IP address.

$IPAddress = (Invoke-WebRequest -Uri ‘http://wtfismyip.com/text’).Content.Trim()
$IPAddress = (Invoke-WebRequest -Uri ‘http://ifconfig.me/ip’).Content.Trim()

$UseableDate = Get-Date -Format 'Dyyyy-MM-dd_Thh-mm-ss-tt'
$IPAddress = (Invoke-WebRequest -Uri 'http://checkip.dyndns.com').Content -replace "[^\d\.]"

The third line in the script, joins the date and time with the IP address and writes (appends) it to the file inside my Dropbox folder. Again, this is running from my home computer. Once in Dropbox at home, my work computer and phone will have the newest version of the file. In fact, it’s become a pretty good reminder that it is lunch time at work, as Dropbox pops up a Notification Area balloon that my file has been updated at 12 p.m.

$UseableDate = Get-Date -Format 'Dyyyy-MM-dd_Thh-mm-ss-tt'
$IPAddress = (Invoke-WebRequest -Uri 'http://checkip.dyndns.com').Content -replace "[^\d\.]"
"$UseableDate -- $IPaddress" | Out-File -FilePath 'C:\Users\tommymaynard\Dropbox\IP\homecomputer-IP.txt' -Append

To be as complete as possible, and save as many seconds as I can per day, I have a little function in my profile on my work computer to grab the IP address from the file. First, here’s an example of my homecomputer-IP.txt file (with hashtags taking the place of the digits):

...
D2015-08-18_T06-00-03-PM -- ##.###.##.###
D2015-08-19_T12-00-06-AM -- ##.###.##.###
D2015-08-19_T06-00-07-AM -- ##.###.##.###
D2015-08-19_T12-00-06-PM -- ##.###.##.###
D2015-08-19_T06-00-03-PM -- ##.###.##.###

The function below will extract the last IP address added to the homecomputer-IP.txt file. With that, I can add it to the Remote Desktop command line executable and get connected to the home computer from anywhere.

Function Get-HomeIP {
    $IPFile = 'C:\Users\tommymaynard\Dropbox\IP\homecomputer-IP.txt'
    (Get-Content -Path $IPFile | Select-Object -Last 1).Split(' -- ')[-1]
}
PS> Get-HomeIP
##.###.##.###
PS> mstsc.exe /v (Get-HomeIP)

That’s it — I hope this might be helpful to someone, someday. It’s saved me a time, or two now.

Update [02/09/2017]: I recently made a couple changes to the above function. The problem was that sometimes the IP wasn’t written to the file, for whatever reason. In this instance, my function ends up returning nothing. Therefore, I now sanitize the file first, so that any rows that don’t contain an IP address are excluded. Take a look.

Function Get-HomeIP {
    $IPFile = 'C:\Users\$env:USERNAME\Dropbox\IP\homecomputer-IP.txt'
    $SantizedIPs = Get-Content -Path $IPFile | Where-Object -FilterScript {$_.Length -gt 34}
    ($SantizedIPs | Select-Object -Last 1).Split(' -- ')[-1]
}