# Short Version

$arr=@();
$arr+=[PSCustomObject]@{From='D:\Someshare';To='\\NEWSERVER\Someshare'}
$arr+=[PSCustomObject]@{From='D:\Test';To='\\NEWSERVER\Test'}

# Normal copying
# $arr|%{emcopy $_.From $_.To /s /de /sdd /purge /secforce /r:1 /w:2 /c /log+:C:\emcopy_log.txt}

# Exclude recycle bins and preserve file owners
$emcopySwitches='/o /s /de /sd /sdd /purge /secforce /r:0 /w:0 /th 32 /c /log+:C:\emcopy_log.txt'
$arr|%{
    $sourceParentFolder=split-path $_.From -Parent   
    $cmdlet="emcopy.exe '$($_.From)' '$($_.To)' $emcopySwitches /xd '$sourceParentFolder\`System Volume Information' '$sourceParentFolder\`$RECYCLE.BIN'"
    write-host $cmdlet    
    Invoke-Expression $cmdlet
}
# Quick-Emcopy.ps1
# Longer Version

# This system backup privilege escalation should be ran only once per session
function addSystemPrivilege{
    param(
        [String[]]$privileges=@("SeBackupPrivilege","SeRestorePrivilege")
    )

    function includeSystemPrivileges{
    $win32api = @'
    using System;
    using System.Runtime.InteropServices;

    namespace SystemPrivilege.Win32API
    {
      [StructLayout(LayoutKind.Sequential)]
      public struct LUID
      {
        public UInt32 LowPart;
        public Int32 HighPart;
      }

      [StructLayout(LayoutKind.Sequential)]
      public struct LUID_AND_ATTRIBUTES
      {
        public LUID Luid;
        public UInt32 Attributes;
      }

      [StructLayout(LayoutKind.Sequential)]
      public struct TOKEN_PRIVILEGES
      {
        public UInt32 PrivilegeCount;
        public LUID Luid;
        public UInt32 Attributes;
      }

      public class Privileges
      {
        public const UInt32 DELETE = 0x00010000;
        public const UInt32 READ_CONTROL = 0x00020000;
        public const UInt32 WRITE_DAC = 0x00040000;
        public const UInt32 WRITE_OWNER = 0x00080000;
        public const UInt32 SYNCHRONIZE = 0x00100000;
        public const UInt32 STANDARD_RIGHTS_ALL = (
                                                    READ_CONTROL |
                                                    WRITE_OWNER |
                                                    WRITE_DAC |
                                                    DELETE |
                                                    SYNCHRONIZE
                                                );
        public const UInt32 STANDARD_RIGHTS_REQUIRED = 0x000F0000u;
        public const UInt32 STANDARD_RIGHTS_READ = 0x00020000u;

        public const UInt32 SE_PRIVILEGE_ENABLED_BY_DEFAULT = 0x00000001u;
        public const UInt32 SE_PRIVILEGE_ENABLED = 0x00000002u;
        public const UInt32 SE_PRIVILEGE_REMOVED = 0x00000004u;
        public const UInt32 SE_PRIVILEGE_USED_FOR_ACCESS = 0x80000000u;

        public const UInt32 TOKEN_QUERY = 0x00000008;
        public const UInt32 TOKEN_ADJUST_PRIVILEGES = 0x00000020;

        public const UInt32 TOKEN_ASSIGN_PRIMARY = 0x00000001u;
        public const UInt32 TOKEN_DUPLICATE = 0x00000002u;
        public const UInt32 TOKEN_IMPERSONATE = 0x00000004u;
        public const UInt32 TOKEN_QUERY_SOURCE = 0x00000010u;
        public const UInt32 TOKEN_ADJUST_GROUPS = 0x00000040u;
        public const UInt32 TOKEN_ADJUST_DEFAULT = 0x00000080u;
        public const UInt32 TOKEN_ADJUST_SESSIONID = 0x00000100u;
        public const UInt32 TOKEN_READ = (
                                          STANDARD_RIGHTS_READ |
                                          TOKEN_QUERY
                                       );
        public const UInt32 TOKEN_ALL_ACCESS = (
                                                STANDARD_RIGHTS_REQUIRED |
                                                TOKEN_ASSIGN_PRIMARY |
                                                TOKEN_DUPLICATE |
                                                TOKEN_IMPERSONATE |
                                                TOKEN_QUERY |
                                                TOKEN_QUERY_SOURCE |
                                                TOKEN_ADJUST_PRIVILEGES |
                                                TOKEN_ADJUST_GROUPS |
                                                TOKEN_ADJUST_DEFAULT |
                                                TOKEN_ADJUST_SESSIONID
                                             );

        [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
        public static extern IntPtr GetCurrentProcess();

        [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
        public static extern IntPtr GetCurrentThread();

        [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
        public static extern bool LookupPrivilegeValue(string lpSystemName, string lpName, out LUID lpLuid);

        [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
        public static extern bool AdjustTokenPrivileges(IntPtr TokenHandle, bool DisableAllPrivileges, ref TOKEN_PRIVILEGES NewState, UInt32 BufferLengthInBytes, IntPtr PreviousStateNull, IntPtr ReturnLengthInBytesNull);

        [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
        public static extern bool OpenProcessToken(IntPtr ProcessHandle, UInt32 DesiredAccess, out IntPtr TokenHandle);

        [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
        public static extern bool OpenThreadToken(IntPtr ThreadHandle, UInt32 DesiredAccess, bool OpenAsSelf, out IntPtr TokenHandle);

        [DllImport("ntdll.dll", EntryPoint = "RtlAdjustPrivilege")]
        public static extern int RtlAdjustPrivilege(
                    UInt32 Privilege,
                    bool Enable,
                    bool CurrentThread,
                    ref bool Enabled
                    );

        [DllImport("Kernel32.dll", SetLastError = true)]
        public static extern bool CloseHandle(IntPtr handle);

        //
        //

        private static LUID LookupPrivilege(string privilegeName)
        {
          LUID privilegeValue = new LUID();
      
          bool res = LookupPrivilegeValue(null, privilegeName, out privilegeValue);

          if (!res)
          {
            throw new Exception("Error: LookupPrivilegeValue()");
          }

          return privilegeValue;
        }

        //
        //

        public static void AdjustPrivilege(string privilegeName, bool enable)
        {
          IntPtr accessToken = IntPtr.Zero;
          bool res = false;

          try
          {
            LUID privilegeValue = LookupPrivilege(privilegeName);

            res = OpenThreadToken(GetCurrentThread(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, false, out accessToken);
        
            if (!res)
            {
              res = OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, out accessToken);

              if (!res)
              {
                throw new Exception("Error: OpenProcessToken()");
              }
            }

            TOKEN_PRIVILEGES tokenPrivileges = new TOKEN_PRIVILEGES();
            tokenPrivileges.PrivilegeCount = 1;
            tokenPrivileges.Luid = privilegeValue;

            if (enable)
            {
              tokenPrivileges.Attributes = SE_PRIVILEGE_ENABLED;
            }
            else
            {
              tokenPrivileges.Attributes = 0;
            }

            res = AdjustTokenPrivileges(accessToken, false, ref tokenPrivileges, (uint)System.Runtime.InteropServices.Marshal.SizeOf(tokenPrivileges), IntPtr.Zero, IntPtr.Zero);
        
            if (!res)
            {
              throw new Exception("Error: AdjustTokenPrivileges()");
            }
          }

          finally
          {
            if (accessToken != IntPtr.Zero)
            {
              CloseHandle(accessToken);
              accessToken = IntPtr.Zero;
            }
          }
        }
      }
    }
'@

  if ([object]::Equals(('SystemPrivilege.Win32API.Privileges' -as [type]), $null)) {
    Add-Type -TypeDefinition $win32api
    }
}
    includeSystemPrivileges;


    $privileges|%{[SystemPrivilege.Win32API.Privileges]::AdjustPrivilege($_, $true)}

    # Validation
    whoami /priv|?{$_ -match "SeBackupPrivilege|SeRestorePrivilege"}
}
addSystemPrivilege;

# Custom variables
$sourceDirectory="H:\HOME"
$destinationDirectory="\\FILESHERVER002\HOME"
$node="NODE05"

# Autogen variables
$logFile="\\snapshots\FileServerClusters\filecopy_logs\$node\quick-emcopy-log.txt"
$log="/LOG+:$logFile"
#$quickEmcopySwitches="/de /s /purge /r:0 /w:0 /c $log"
$quickEmcopySwitches="/o /s /de /sd /sdd /purge /secforce /r:1 /w:2 /th 32 /c $log"

function quickEmcopy{
    param(
    $sourceDirectory,
    $destinationDirectory,
    $logFile
    )
    $timer=[System.Diagnostics.Stopwatch]::StartNew();
    $log="/LOG+:$logFile";
    $message="=========================QuickEmcopy is starting on $env:computername at $(get-date -Format "yyyy-MM-dd-hh:mm:ss")=========================`r`n=========================Source: $sourceDirectory => Destination: $destinationDirectory=========================";
    write-host $message;
    Add-Content $logFile $message;

function expandZipfile($file, $destination){
    $shell = new-object -com shell.application
    $zip = $shell.NameSpace($file)
    
    foreach($item in $zip.items()){
    $shell.Namespace($destination).copyhere($item)
    }
}   
    function installEmcopy{
        $emcopyIsInstalled=(Get-Command emcopy.exe -ErrorAction SilentlyContinue) # Deterministic check on whether emcopy is already available on this system
        if (!($emcopyIsInstalled)){
            $tempDir="C:\Temp";
            $extractionDir="C:\Windows"
            $emCopySource = "https://blog.kimconnect.com/wp-content/uploads/2019/08/emcopy.zip";
            $destinationFile = "$tempDir\emcopy.zip";
            try{[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12}catch{}
            New-Item -ItemType Directory -Force -Path $tempDir
            New-Item -ItemType Directory -Force -Path $extractionDir
            $webclient = New-Object System.Net.WebClient;
            $WebClient.DownloadFile($emCopySource,$destinationFile);
            expandZipfile $destinationFile -Destination $extractionDir
        }else{
            "EMCOPY is currently available in this system.`n";
        }    
    }

    if(!(Get-Command emcopy.exe -ea silentlycontinue)){installEmcopy};

    $sourceParentFolder=split-path $sourceDirectory -Parent     
    $expression="emcopy.exe '$sourceDirectory' '$destinationDirectory' $quickEmcopySwitches /xd '$sourceParentFolder`System Volume Information' '$sourceParentFolder`$RECYCLE.BIN'";
        try{
            write-host $expression;
            Invoke-Expression $expression;          
        }
        catch{
            write-host "There were errors`r`n $Error";
        }

    $hoursElapsed=[math]::round($timer.Elapsed.TotalHours,2);
    $message="`r`n=========================QuickEmcopy has completed at $(get-date -Format "yyyy-MM-dd-hh:mm:ss") - Total Hours: $hoursElapsed=========================";
    Add-Content $logFile $message;
    return $timer.Elapsed.TotalHours
}

quickEmcopy -sourceDirectory $sourceDirectory -destinationDirectory $destinationDirectory -logFile $logFile