SinkProfile.psm1

# -----
#region Public New-SinkProfileFixture
Function New-SinkProfileFixture {
<#
.SYNOPSIS
    The New-SinkProfileFixture function builds out the fixture code for the CurrentUserAllHosts proifle script.
.DESCRIPTION
    The CurrentUserAllHosts profile script for Windows PowerShell is located at C:\Users\<username>\Documents\WindowsPowerShell\profile.ps1. If this file already exists in this location, it will be backed up into the same directory location as profile.ps1.<Date>.bak. This function will use three default computer names to create the new profile.ps1 file. These computer names (computer1, computer2, and computer3) will likely not be the computers on which you inteded to use the synced profile. Therefore, be certain to include all the computer names when this function in invoked.
.PARAMETER ComputerName
    The ComputerName parameter accepts one, or more, comma-seperated computers name on which the resulting profile.ps1 profile script is intended to be used. Using this function against one computer defeats the purpose, as the idea behind the SinkProfile is to sync a CurrentUserAllHosts profile between multiple computers using a service such as Dropbox, Box, etc.
.EXAMPLE
    New-SinkProfileFixture -ComputerName WorkDesktop,HomeDesktop,HomeLaptop
        #region Code for all computers.
        Switch ($env:COMPUTERNAME) {
            {$_ -eq 'WorkDesktop'} {
                # Enter code here for the "WorkDesktop" computer.
            }
 
            {$_ -eq 'HomeDesktop'} {
                # Enter code here for the "HomeDesktop" computer.
            }
 
            {$_ -eq 'HomeLaptop'} {
                # Enter code here for the "HomeLaptop" computer.
            }
 
            {$_ -eq 'WorkDesktop' -or $_ -eq 'HomeDesktop' -or $_ -eq 'HomeLaptop'} {
                # Enter code here for all 3 computers.
                $SinkProfile = $true
            }
 
            default {}
        } # End Switch.
        #endregion.
 
        #region Invoke Invoke-SinkProfileSync function.
        Invoke-SinkProfileSync
        #endregion.
 
        This example will create a Sink Profile CurrentUserAllHosts profile.ps1 profile script to be used on the above, included computers.
.NOTES
    Name: New-SinkProfileFixture
    Author: Tommy Maynard
#>

    [CmdletBinding()]
    Param (
        [Parameter()]
        [string[]]$ComputerName = ('computer1','computer2','computer3')
    )

    Begin {
        Write-Verbose -Message 'Determining the existence of the $PROFILE variable.'
        If (GetSPProfileVariable) {

            Write-Verbose -Message 'Determining the existence of the $PROFILE CurrentUserAllHosts property.'
            If (GetSPProfileVariableProperty) {
                Write-Verbose -Message 'Determining if a current CurrentUserAllHosts profile script needs backup.'
                $ProfileNeeds = GetSPProfileNeed

            } Else {
                'A $PROFILE variable CurrentUserAllHosts property does not exist.' | ForEach-Object {
                    Write-Warning -Message $_; Write-Verbose -Message $_
                }
                break
            } # End If-Else.
        } Else {
            'A $PROFILE variable does not exist.' | ForEach-Object {
                Write-Warning -Message $_; Write-Verbose -Message $_
            }
            break
        } # End If-Else.
    } # End Begin.

    Process {
        If ($ProfileNeeds -contains 'backup') {
            Write-Verbose -Message 'Backing up existing CurrentUserAllHosts profile script.'
        }
        Write-Verbose -Message 'Creating a new CurrentUserAllHosts profile script.'
        StartSPBackupAndCreate -ProfileNeeds $ProfileNeeds
    } # End Process.

    End {
        Write-Verbose -Message 'Adding the fixture code to the new CurrentUserAllHosts profile script.'
        NewSPProfileScript -ComputerName $ComputerName
    } # End End.
} # End Function: New-SinkProfileFixture.
#endregion.

#region Private GetSPProfileVariable.
Function GetSPProfileVariable {
<#
Called by: New-SinkProfileFixture.
Purpose: The purpose of this helper function is to
#>

    If ($PROFILE) {
        $true
    } Else {
        $false
    } # End If-Else.
} # End Function: GetSPProfileVariable.
#endregion.

#region Private GetSPProfileVariableProperty.
Function GetSPProfileVariableProperty {
<#
Called by: New-SinkProfileFixture.
Purpose: The purpose of this helper function is to
#>

    If (Get-Member -InputObject $PROFILE -Name CurrentUserAllHosts -MemberType NoteProperty) {
        $true
    } Else {
        $false
    } # End If-Else.
} # End Function: GetSPProfileVariableProperty.
#endregion.

#region Private GetSPProfileNeed.
Function GetSPProfileNeed {
<#
Called by: New-SinkProfileFixture.
Purpose: The purpose of this helper function is to
#>

    If (Test-Path -Path $PROFILE.CurrentUserAllHosts) {
        'backup','create'
    } Else {
        'create'
    } # End If-Else.
} # End Function: GetSPProfileNeed.
#endregion.

#region Private StartSPBackupAndCreate.
Function StartSPBackupAndCreate {
<#
Called by: New-SinkProfileFixture.
Purpose: The purpose of this helper function is to
#>

    Param (
        [Parameter()]
        [string[]]$ProfileNeeds
    )

    $ProfilePath = $PROFILE.CurrentUserAllHosts
    Switch ($ProfileNeeds) {
        'backup' {
            Get-Item -Path $ProfilePath | Copy-Item -Destination "$ProfilePath.$(Get-Date -Format 'DyyMMddTHHmmss.fff').bak"
        }
        'create' {
            New-Item -Path $ProfilePath -Force | Out-Null
        }
    } # End Switch.
} # End Function: StartSPBackupAndCreate.
#endregion.

#region Private NewSPProfileScript.
Function NewSPProfileScript {
<#
Called by: New-SinkProfileFixture.
Purpose: The purpose of this helper function is to
#>

    Param (
        [Parameter()]
        [string[]]$ComputerName
    )

    $FixtureContent = @"
#region Code for all computers.
Switch (`$env:COMPUTERNAME) {
"@

    Foreach ($Computer in $ComputerName) {
$FixtureContent += @"
 
    {`$_ -eq '$Computer'} {
        # Enter code here for the "$Computer" computer.
    }
 
"@

    } # End ForEach-Object.
$FixtureContent += @"
 
    {`$_ -eq $("'" + ($ComputerName -join "' -or `$_ -eq '") + "'")} {
        # Enter code here for all $($ComputerName.Count) computers.
        `$SinkProfile = `$true
    }
 
    default {}
} # End Switch.
#endregion.
 
#region Invoke Invoke-SinkProfileSync function.
Invoke-SinkProfileSync
#endregion.
"@


    Set-Content -Path $Profile.CurrentUserAllHosts -Value $FixtureContent
} # End Function: NewSPProfileScript.
#endregion.
# -----
#region Public Add-SinkProfilePartner.
Function Add-SinkProfilePartner {
<#
.SYNOPSIS
    The Add-SinkProfilePartner function creates a partnership with a service that allows syncing of files between multiple computers.
.DESCRIPTION
    The function uses helper functions in order to create a directory called SinkProfile inside the current user's Documents folder, such as "C:\Users\tommymaynard\Documents\SinkProfile\",
    create a directory called SinkProfile inside the current user's sync partner (ie Dropbox), such as "C:\Users\tommymaynard\Dropbox\SinkProfile\", and
.PARAMETER PartnerName
    The PartnerName parameter is the name of the selective service, such as Dropbox, that will be used with the SinkProfile PowerShell module.
.PARAMETER PartnerPath
    The PartnerPath parameter requires a path value to the folder that syncs with the chosen syncing service, such as Dropbox. If the "C:\Users\tommymaynard\Dropbox" folder syncs with Dropbox, this value would be entered as the value for the PartnerPath parameter.
.EXAMPLE
    Add-SinkProfilePartner -PartnerName Dropbox -PartnerPath 'C:\Users\tommymaynard\Dropbox'
    This example creates a SinkProfile partner file for Dropbox using the partner name of "Dropbox." This same file will also be stored in C:\Users\tommymaynard\Documents\SinkProfile.
.EXAMPLE
    Add-SinkProfilePartner -PartnerName Box -PartnerPath 'C:\Users\tommymaynard\Box Sync\'
    This example create a SinkProfile partner file for Box using the partner name of "Box." Notice the difference in this example, in that PartnerName value differs from the final directory of the PartnerPath value (Box Sync).
    This same file will also be stored in C:\Users\tommymaynard\Documents\SinkProfile.
.NOTES
    Name: Add-SinkProfilePartner
    Author: Tommy Maynard
#>

    [CmdletBinding()]
    Param (
        [Parameter(Mandatory)]
        [string]$PartnerName,

        [Parameter(Mandatory)]
        [string]$PartnerPath,

        [Parameter()]
        [switch]$Force
    )

    Begin {
        $DocumentsPath = "$env:USERPROFILE\Documents\SinkProfile"
        $PartnerPath = "$($PartnerPath.TrimEnd('\'))\SinkProfile"
    } # End Begin.

    Process {
        If (NewSPDocumentsDirectory -DocumentsPath $DocumentsPath) {
            Write-Verbose -Message "Creating local storage location in ""$DocumentsPath."""

            If (NewSPPartnerDirectory -PartnerPath $PartnerPath) {
                Write-Verbose -Message "Creating partner storage location in ""$PartnerPath."""
                
                $Params = @{
                    PartnerName = $PartnerName
                    PartnerPath = $PartnerPath
                    DocumentsPath = $DocumentsPath
                }

                If (-Not(Test-Path -Path "$(Join-Path -Path $PartnerPath -ChildPath '\profile.ps1')")) {
                    Write-Verbose -Message "Creating a partner configuration file."
                    NewSPDocumentsAndPartnerFile @Params
                }
            }

        } Else {
            "Unable to create or verify directory." | ForEach-Object {
                Write-Warning -Message $_; Write-Verbose -Message $_
            } # End ForEach-Object.
        } # End If-Else.
    } # End Process.

    End {} # End End.
} # End Function: Add-SinkProfilePartner.
#endregion.

#region Private NewSPDocumentsDirectory.
Function NewSPDocumentsDirectory {
<#
Called by: Add-SinkProfilePartner.
Purpose: The purpose of this helper function is to create a directory called
            SinkProfile inside the current user's Documents folder, such as
            "C:\Users\tommymaynard\Documents\SinkProfile\".
#>

    Param (
        [Parameter()]
        [string]$DocumentsPath
    )

    If (-Not(Test-Path -Path $DocumentsPath)) {
        New-Item -Path $DocumentsPath -ItemType Directory | Out-Null
        Start-Sleep -Milliseconds 500

        If ((Get-Item -Path $DocumentsPath).Exists) {
            $true
        } Else {
            $false
        } # End If-Else.

    } Else {
        $true
    } # End If-Else.
} # End Function: NewSPDocumentsDirectory.
#endregion.

#region Private NewSPPartnerDirectory.
Function NewSPPartnerDirectory {
<#
Called by: Add-SinkProfilePartner.
Purpose: The purpose of this helper function is to create a directory called
            SinkProfile inside the current user's sync partner (ie Dropbox),
            such as "C:\Users\tommymaynard\Dropbox\SinkProfile\".
#>

    Param (
        [Parameter()]
        [string]$PartnerPath
    )

    If (-Not(Test-Path -Path $PartnerPath)) {
        New-Item -Path $PartnerPath -ItemType Directory | Out-Null
        Start-Sleep -Milliseconds 500

        If ((Get-Item -Path $PartnerPath).Exists) {
            $true
        } Else {
            $false
        } # End If-Else.

    } Else {
        $true
    } # End If-Else.
} # End Function: NewSPPartnerDirectory.
#endregion.

#region Private NewSPDocumentsAndPartnerFile.
Function NewSPDocumentsAndPartnerFile {
<#
Called by: Add-SinkProfilePartner.
Purpose: The purpose of this helper function is to create a sync partner file in both
            the Documents and Partner directories, such as "C:\Users\tommymaynard\Documents\SinkProfile\"
            and "C:\Users\tommymaynard\Dropbox\SinkProfile\".
#>

    Param (
        [Parameter()]
        [string]$PartnerName,

        [Parameter()]
        [string]$PartnerPath,

        [Parameter()]
        [string]$DocumentsPath
    )

    $Object = [pscustomobject]@{
        PartnerName = $PartnerName
        PartnerPath = $PartnerPath
        PartnerPathProfile = "$($PartnerPath)\profile.ps1"
        PartnerPathXml = "$(Join-Path -Path $PartnerPath -ChildPath $PartnerName).xml"
        DocumentsPath = $DocumentsPath
        DocumentsPathXml = "$(Join-Path -Path $DocumentsPath -ChildPath $PartnerName).xml"
    }

    try {
        $Object | Export-Clixml -Path $Object.PartnerPathXml
        $Object | Export-Clixml -Path $Object.DocumentsPathXml
    } catch {
        Write-Warning -Message 'An exception or error occurred.'
    } # End try-catch.
} # End Function: NewSPDocumentsAndPartnerFile.
#endregion.
# -----
#region Public Get-SinkProfilePartner.
Function Get-SinkProfilePartner {
<#
.SYNOPSIS
    The Get-SinkProfilePartner function returns Sink Profile partners registered on the local computer.
.DESCRIPTION
    A Sink Profile partner is considered to be registered when a specific XML file has been created and is located in the following directory: "C:\Users\<username>\Documents\SinkProfile\." This function relies on at least one helper function in order to create a Sink Profile partner object that is then displayed by this function.
.EXAMPLE
    Get-SinkProfilePartner
    PartnerName : Box
        PartnerPath : C:\users\tommymaynard\Box Sync\SinkProfile
        PartnerPathProfile : C:\users\tommymaynard\Box Sync\SinkProfile\profile.ps1
        PartnerPathXml : C:\users\tommymaynard\Box Sync\SinkProfile\Box.xml
        DocumentsPath : C:\Users\tommymaynard\Documents\SinkProfile
        DocumentsPathXml : C:\Users\tommymaynard\Documents\SinkProfile\Box.xml
 
        In this example, the contents of all Sink Profile partner files are read in, and an object is created for each file.
.NOTES
    Name: Get-SinkProfilePartner
    Author: Tommy Maynard
#>

    [CmdletBinding()]
    Param ()

    Begin {
        $DocumentsPath = "$env:USERPROFILE\Documents\SinkProfile\"
    } # End Begin.

    Process {
        If (Test-Path -Path $DocumentsPath) {
            NewSPPartnerObject -DocumentsPath $DocumentsPath
        } Else {
            "Unable to locate the Sink Profile Documents directory: ""$DocumentsPath.""" | ForEach-Object {
                Write-Warning -Message $_; Write-Verbose -Message $_
            }
            break
        } # End If-Else.
    } # End Process.

    End {} # End End.
} # End Function: Get-SinkProfilePartner.
#endregion.

#region Private NewSPPartnerObject.
Function NewSPPartnerObject {
<#
Called by: Get-SinkProfilePartner.
Purpose: The purpose of this helper function is to return the SinkProfile partners on the local computer
            from "C:\Users\<username>\Documents\SinkProfile\". Each XML file in this directory represents
            a seperate SinkProfile partner.
#>

    [CmdletBinding()]
    Param (
        [Parameter(Mandatory)]
        [string]$DocumentsPath
    )
    #region Create SinkProfile partner object.
    Get-ChildItem -Path $DocumentsPath -Filter '*.xml' | ForEach-Object {
        Import-Clixml -Path $_.FullName
    } # End ForEach-Object.
    #endregion.
} # End Function: NewSPPartnerObject.
#endregion.
# -----
#region Public Invoke-SinkProfileSync.
Function Invoke-SinkProfileSync {
<#
.SYNOPSIS
    The Invoke-SinkProfileSync function ensures the newest version of the CurrentUserAllHosts profile script is useable in the local file system.
.DESCRIPTION
    If the newest version of the CurrentUserAllHosts profile script is newer on the computer vs. in the partner folder, it will copy it from the its place in the WindowsPowerShell folder (C:\Users\<username>\Documents\WindowsPowerShell\), to its place in the partner folder, such as C:\Users\<username>\Dropbox\SinkProfile\profile.ps1. This function makes use of the Get-SinkProfilePartner public function.
.EXAMPLE
    Invoke-SinkProfileSync
    This example copies the newest CurrentUserAllHosts profile script from the location with the newest version to the location of the oldest version.
.NOTES
    Name: Invoke-SinkProfileSync
    Author: Tommy Maynard
#>

    [CmdletBinding()]
    Param ()

    Begin {} # End Begin.

    Process {
        #region Sync of all Sink Profile partners.
        Get-SinkProfilePartner | ForEach-Object {
            $Params = @{
                PartnerPathProfile = $_.PartnerPathProfile
                ProfileScriptLastWrite = (Get-Item -Path $PROFILE.CurrentUserAllHosts).LastWriteTime
                PartnerProfileScriptLastWrite = If ((Get-Item $_.PartnerPathProfile -OutVariable LWT -ErrorAction SilentlyContinue).LastWriteTime) {
                    $LWT.LastWriteTime
                } Else {
                    Get-Date -Date '01/01/1600'
                }
            }
            $Params += @{
                LocalProfileScriptNewest = $Params.ProfileScriptLastWrite -gt $Params.PartnerProfileScriptLastWrite
                MatchingProfileScripts = $Params.PartnerProfileScriptLastWrite -eq $Params.ProfileScriptLastWrite
            }

            If ($Params.MatchingProfileScripts -eq $true) {
                $Sync = $false
                Write-Verbose -Message 'Copying profile script is not necessary in either direction.'
            } Else {

                $Sync = $true
                If ($Params.LocalProfileScriptNewest -eq $false) {
                    Write-Verbose -Message 'Copying profile script from partner to local system.'

                } ElseIf ($Params.LocalProfileScriptNewest -eq $true) {
                    Write-Verbose -Message 'Copying profile script from local system to partner.'
                } # End If-ElseIf.
            } # End If-Else.
        } # End ForEach-Object.
        #endregion.
    } # End Process.

    End {
        If ($Sync) {
            $Params = @{
                PartnerPathProfile = $Params.PartnerPathProfile
                LocalProfileScriptNewest = $Params.LocalProfileScriptNewest
            }
            SyncSPProfileScript @Params
        }
    } # End End.
} # End Function: Invoke-SinkProfileSync.
#endregion.

#region Private SyncSPProfileScript.
Function SyncSPProfileScript {
<#
Called by: Invoke-SinkProfileSync.
Purpose: The purpose of this helper function is to sync the profile.ps1 file
            from the location that holds the newest version of the profile script
            to the location that hold the oldest version.
#>

    [CmdletBinding()]
    Param (
        [Parameter()]
        [string]$PartnerPathProfile,

        [Parameter()]
        [string]$LocalProfileScriptNewest
    )

    Begin {} # End Begin.

    Process {
        If ($LocalProfileScriptNewest -eq $false) {
            #$ProfileScriptLastWrite -lt $PartnerProfileScriptLastWrite
            Copy-Item -Path $PartnerPathProfile -Destination $PROFILE.CurrentUserAllHosts

            Write-Output -InputObject 'The SinkProfile profile script has been updated.'
            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 (Get-Process -Id $PID).ProcessName
                Stop-Process -Id $PID
            } # End If.

        } ElseIf ($LocalProfileScriptNewest -eq $true) {
            #$ProfileScriptLastWrite -gt $PartnerProfileScriptLastWrite
            Copy-Item -Path $PROFILE.CurrentUserAllHosts -Destination $PartnerPathProfile
        } # End If-ElseIf.
    } # End Process.

    End {} # End End.
} # End Function: SyncSPProfileScript.
#endregion.
# -----
#region Public Complete-SinkProfilePartner.s
Function Complete-SinkProfilePartner {
<#
.SYNOPSIS
    The Complete-SinkProfilePartner function completes a partnership with a service that allows syncing of files between multiple computers.
.DESCRIPTION
    This function is to be used on computers that are a part of a sync group; however, it should only be invoked on computers after the initial work is completed on the first computer in the sync group. The second (third, fourth, etc.) computer's name should have been a parameter value provided the ComputerName parameter on the New-SinkProfileFixture function ran on the initial computer in the sync group. This is what ensures the profile script will apply to all the computers in the sync group.
.PARAMETER PartnerPath
    The PartnerPath parameter requires a path value to the folder that syncs with the chosen syncing service, such as Dropbox. If the "C:\Users\tommymaynard\Dropbox" folder syncs with Dropbox, this value would be entered as the value for the PartnerPath parameter.
.EXAMPLE
    Complete-SinkProfilePartner -
.NOTES
    Name: Complete-SinkProfilePartner
    Author: Tommy Maynard
#>

    [CmdletBinding()]
    Param (
        [Parameter(Mandatory)]
        [string]$PartnerName,

        [Parameter(Mandatory)]
        [string]$PartnerPath
    )

    Begin {
        $DocumentsPath = "$env:USERPROFILE\Documents\SinkProfile"
        $PartnerFilePath = "$PartnerPath\SinkProfile\$PartnerName.xml"

    } # End Begin.

    Process {
        If (Test-Path -Path $PartnerFilePath) {
            $PartnerPathsFromFile = Import-Clixml -Path $PartnerFilePath

            Write-Verbose -Message 'Attempting to back up existing CurrentUserAllHosts profile script.'
            StartSPBackup

            Write-Verbose -Message 'Attempting to copy CurrentUserAllHosts profile script to the local system.'
            CopySPProfileScript -PartnerPathProfile $PartnerPathsFromFile.PartnerPathProfile

            Write-Verbose -Message "Attempting to create a local storage location in ""$DocumentsPath."""
            NewSPDocumentsDirectory -DocumentsPath $DocumentsPath | Out-Null # Also used by Add-SinkProfilePartner.

            Write-Verbose -Message 'Attempting to copy partner file to local system.'
            CopySPPartnerFile -PartnerPathXml $PartnerPathsFromFile.PartnerPathXml -DocumentsPath $DocumentsPath
        } Else {
            Write-Warning -Message 'Unable to locate partner file in partner path.'
        }
    } # End Process.

    End {} # End End.
} #End Function: Complete-SinkProfilePartner.
#endregion.

#region Private StartSPBackup.
Function StartSPBackup {
<#
Called by: Complete-SinkProfilePartner.
Purpose: The purpose of this helper function is to backup the CurrentUserAllHosts
            (profile.ps1) profile script, if it already exists. The "2" in the
            filename indicates it was the second (or third, fourth, etc.) computer
            in the SinkProfile sync group.
#>

    $ProfilePath = $PROFILE.CurrentUserAllHosts
    If (Test-Path -Path $ProfilePath) {
        Get-Item -Path $ProfilePath |
            Copy-Item -Destination "$ProfilePath.$(Get-Date -Format 'DyyMMddTHHmmss.fff').2.bak"
    }
} # End Function: StartSPBackup.
#endregion.

#region Private CopySPProfileScript.
Function CopySPProfileScript {
<#
Called by: Complete-SinkProfilePartner.
Purpose: The purpose of this helper function is to copy the CurrentUserAllHosts
            profile script from the partner folder (Dropbox, etc.) to the local
            system.
#>

    Param (
        [Parameter(Mandatory)]
        [string]$PartnerPathProfile
    )
    Copy-Item -Path $PartnerPathProfile -Destination (Split-Path -Path $PROFILE.CurrentUserAllHosts)
} # End Function: CopySPProfileScript.
#endregion.

#region Private NewSPDocumentsDirectory.
#--> Already included above.
#endregion.

#region Private CopySPPartnerFile.
Function CopySPPartnerFile {
<#
Called by: Complete-SinkProfilePartner.
Purpose: The purpose of this helper function is to copy the partner file from
            the partner folder (Dropbox, etc.) to the local system.
#>

    Param (
        [Parameter(Mandatory)]
        [string]$PartnerPathXml,

        [Parameter()]
        [string]$DocumentsPath
    )
    Copy-Item -Path $PartnerPathXml -Destination $DocumentsPath
} # End Function: CopySPPartnerFile.
#endregion