Functions/Copy-AxiumFiles.ps1

function Copy-AxiumFiles {
<#
.SYNOPSIS
    Used to copy axiUm files. Usually run as a startup or logon script.
.DESCRIPTION
    Used to copy axiUm files. Usually run as a startup or logon script. Can be limited to only
    copy files when connected to a given network, not connected to a given network, or both.
    This is useful to copy files when connected to your organization's network, but not through
    on offsite VPN connection which may potentially be slow.
 
    Uses RoboCopy under the hood, so it requires RoboCopy to be located in the path, which is the
    case by default in Windows Vista and latter. RoboCopy will log to $ClientPath\Copy-AxiumFiles.log.
 
    cpaf is an alias of this.
.PARAMETER ClientPath
    Where axiUm is installed.
.PARAMETER SourcePathOrPrefix
    If MultipleCopies is set, the name of the last folder in ClientPath will be appended to SourcePathOrPrefix
    to get where to copy files from.
 
    Otherwise, SourcePathOrPrefix will be treated as the complete path of where the files should be
    copied from.
.PARAMETER RequireInSubnet
    An array of 2 IP addresses, the first being the subnet, and the second the subnet mask.
    Files will only be copied if an IP address IS found connected to this subnet. If not set, this
    will not be used to determine if files should be copied.
.PARAMETER RequireNotInSubnet
    An array of 2 IP addresses, the first being the subnet, and the second the subnet mask.
    Files will only be copied if an IP address IS NOT found connected to this subnet. If not set, this
    will not be used to determine if files should be copied.
.PARAMETER CopyAll
    By default, the only files copied are those that are only in the source, or have been
    modified in the source since they were last copied. This switch changes that behavior to copy ALL
    files every time this script is run.
.PARAMETER MultipleCopies
    Set this if the computer(s) you are running this on may have more than copy installed. This might be
    the case if you have a test copy of axiUm that is on a different version from your production instance,
    thus requiring a separate install.
.PARAMETER AppendToLog
    By default, log files will be cleared before being written to. This changes that behavior to just append
    to the existing log. Use this carefully, as, in certain circumstances, you could get log files that grow
    to a very large size (such as if this function is called in a logon or startup script, and you do not have
    another method in place for clearing the log files).
.INPUTS
    System.IO.DirectoryInfo
.EXAMPLE
    PS> Copy-AxiumFiles -ClientPath 'C:\axiUm' -SourcePathOrPrefix $PSScriptRoot
 
    Copies the files for a single instance of axiUm from the directory this script is in to "C:\axiUm", writing a
    log file to C:\axiUm\Copy-AxiumFiles.log. Only new and updated files are copied, and the IP addresses of the
    device are not considered.
.EXAMPLE
    PS> Copy-AxiumFiles -ClientPath 'C:\axiUm' -SourcePathOrPrefix $PSScriptRoot -CopyAll
 
    Same as Example 1, but copies all files.
.EXAMPLE
    PS> Copy-AxiumFiles -ClientPath 'C:\axiUm' -SourcePathOrPrefix $PSScriptRoot -RequireInSubnet @('10.0.0.0', '255.0.0.0') -RequireNotInSubnet @('10.2.0.0', '255.255.0.0)
 
    Let us say that your organization has IP addresses in 10.0.0.0/255.0.0.0, but your VPN uses a small part of
    that (10.1.0.0/255.255.0.0). This would do the same as Example 1, but only if the workstation this was run on
    was connected to your organization's network, but not through VPN.
 
    This is useful if you want to push out a lot of files, but are worried about doing this when somebody is
    connected via a slow offsite wifi connection to your VPN. This would skip those workstations, which you
    could then handle manually.
.EXAMPLE
    PS> Get-ChildItem -Path 'C:\axiUm' -Directory | Copy-AxiumFiles -SourcePathOrPrefix '\\domain\axiUm-ClientFiles-' -MultipleCopies
 
    This is an example of using the MultipleCopies switch to copy files for multiple copies of axiUm. Let us assume
    there are two installations of axiUm on the workstation this is being run on:
        * C:\axiUm\Production
        * C:\axiUm\Test
     
    This will:
        * Copy all files that are not in or newer than the ones in C:\axiUm\Production from \\domain\axiUm-ClientFiles-Production
          to C:\axiUm\Production.
        * Copy all files that are not in or newer than the ones in C:\axiUm\Test from \\domain\axiUm-ClientFiles-Test
          to C:\axiUm\Test.
.NOTES
    Author : Dan Thompson
    Copyright : 2020 Case Western Reserve University
#>


[CmdletBinding(SupportsShouldProcess)]

param(
    [Parameter(
        Position = 0,
        ValueFromPipeline = $True,
        ValueFromPipelineByPropertyName = $True,
        Mandatory = $True
    )]
    [ValidateNotNullOrEmpty()]
    [ValidateScript({
        if (-not ($_ | Test-Path)) {
            throw "$($_.FullName) doesn't exist."
        }

        return $True
    })]
    [Alias('clp')]
    [System.IO.DirectoryInfo]$ClientPath,

    [Parameter(Mandatory = $True)]
    [ValidateNotNullOrEmpty()]
    [Alias('spp')]
    [string]$SourcePathOrPrefix,

    [ValidateCount(2,2)]
    [Alias('ris', 'insubnet', 'in_subnet')]
    [System.Net.IPAddress[]]$RequireInSubnet,

    [ValidateCount(2,2)]
    [Alias('rnis', 'notinsubnet', 'not_in_subnet')]
    [System.Net.IPAddress[]]$RequireNotInSubnet,

    [Alias('cpa')]
    [switch]$CopyAll,

    [Alias('mi')]
    [switch]$MultipleCopies,

    [Alias('al')]
    [switch]$AppendToLog
)

begin {
    # Abort if RoboCopy isn't in the path.
    if ($Null -eq (Get-Command -Name 'robocopy' -ErrorAction SilentlyContinue)) {
        throw 'RoboCopy was not found in your path. Aborting.'
    }

    # Determine if we meet the requirements set forth to copy files.

    $SubnetRequirements = @{}

    if ($PSBoundParameters.ContainsKey('RequireInSubnet') -and ($Null -ne $RequireInSubnet)) {
        $SubnetRequirements.InSubnet = $RequireInSubnet
    }

    if ($PSBoundParameters.ContainsKey('RequireNotInSubnet') -and ($Null -ne $RequireNotInSubnet)) {
        $SubnetRequirements.NotInSubnet = $RequireNotInSubnet
    }

    $CanCopy = (Get-IPAddresses | Test-IPAddressRequirementsMet @SubnetRequirements).Contains($True)

    if ($CanCopy) {
        Write-Verbose -Message 'Met requirements to copy files.'
    } else {
        Write-Warning -Message 'Requirements to copy files not met, so not doing anything. Bye now!'
    }
}

process {
    if ($CanCopy) {
        # Get the actual source path. This will be different from $SourcePathOrPrefix if we have multiple copies
        # of axiUm.
        [System.IO.DirectoryInfo]$SourcePath = $SourcePathOrPrefix
        if ($MultipleCopies.IsPresent) {
            $SourcePath = $SourcePathOrPrefix + $(Split-Path -Path $ClientPath -Leaf)
        }

        # Check if we have source files for the copy of axiUm.
        $HaveSourceFiles = $True
        if ($MultipleCopies.IsPresent) {
            $HaveSourceFiles = Test-Path -Path $SourcePath -PathType 'Container'
        }

        if ($HaveSourceFiles) {
            # We do, so we are good to copy the files.
            Write-Verbose -Message "$($SourcePath.FullName) exists, so copying contents to $($ClientPath.FullName) ..."

            # Set up some RoboCopy options. We have to do this here as doing it in begin will cause
            # $RobocopyOptions to not get emptied for each copy of axiUm.
            
            $RobocopyOptions = @('/E')
            if (-not $CopyAll.IsPresent) {
                $RobocopyOptions += '/XO'
            }

            [System.IO.FileInfo]$LogPath = Join-Path -Path $ClientPath.FullName -ChildPath 'Copy-AxiumFiles.log'

            $RobocopyLogFlag = '/UNILOG'
            if ($AppendToLog.IsPresent) {
                $RobocopyLogFlag += '+'
            }
            $RobocopyLogFlag += ":$($LogPath.FullName)"

            $RobocopyOptions += $RobocopyLogFlag

            # Call RoboCopy.

            $RobocopyArgs = @($SourcePath.FullName, $ClientPath.FullName) + $RobocopyOptions

            if ($PSCmdlet.ShouldProcess("robocopy.exe $RobocopyArgs", 'Run')) {
                & robocopy.exe $RobocopyArgs
            }
        } else {
            # We don't.
            Write-Warning -Message "$($SourcePath.FullName) doesn't exist, or is not a directory. Not copying contents to $($ClientPath.FullName)."
        }
    }
}
}