PowerShell: Synchronize Files Between Different Domains

<# File_Copy_Script_UNC_to_Local_V0.1.ps1
Purpose: connect to an external domain to copy files onto a Intranet server.

Features:
- Synchronize data between different domains
- Output logs onto C:\scripts\logs (or whichever directory stores the script)

Requirements:
- Robocopy version XP027 5.1.10.1027 or higher (Windows 2008 Server and above)
- Valid Extranet CIFS (SMB) credentials

Usage:
- Edit the indicated section to provide appropriate credentials and storage paths
- Create a Windows scheduled task (taskschd.msc) with "powershell.exe -executionpolicy bypass C:\scripts\File_Copy_Script_UNC_to_Local_V0.1.ps1 -runtype $true"

Future Development:
- Save passwords as encrypted characters inside an XML file, instead of plain text.
- Automatically add script to scheduled tasks
- Send email notifications upon completion of each execution
#>

################################# Edit Only This Section ####################################################
$sourceDomainUser="EXTRANET.KIMCONNECT.COM\BALOO"
$plainTextPassword="PASWORDHERE"
$sourceDomainPassword = ConvertTo-SecureString $plainTextPassword -AsPlainText -Force
$sourceCredential = New-Object System.Management.Automation.PSCredential ($sourceDomainUser, $sourceDomainPassword)

$arr=@{}
$arr["from"] = @{}; $arr["to"] = @{}
$baseUNC="\\EXTRANET.KIMCONNECT.COM\CIFS";$baseLocalShare="D:\SHARES";
$arr["from"] = @("$baseUNC\SHARE1"); $arr["to"]=@("$baseLocalShare\SHARE1")
$arr["from"] += "$baseUNC\SHARE2"; $arr["to"]+="H:\SHARE2"

################################# Edit Only Above This Line #################################################
$dateStamp = Get-Date -Format "yyyy-MM-dd-hhmmss"
$scriptName=$MyInvocation.MyCommand.Path
$scriptPath=Split-Path -Path $scriptName
$logPath="$scriptPath\logs"
if(!(Test-Path $logPath)){New-Item -ItemType Directory -Force -Path $logPath}
$logFile="$logPath\robocopy-log-$dateStamp.txt"
$log="/LOG+:'$logFile'"
$switches="/MIR /R:0 /W:0 /XO /XJD /XJF /FFT /MT:32 /TBD /NP "
$GLOBAL:pathErrors="";
$stopWatch= [System.Diagnostics.Stopwatch]::StartNew()

# Paths validation
$sourcePathErrors="";
$destinationPathErrors="";
cls;
for ($i=0; $i -lt $arr.from.length; $i++){
$from=$arr.from[$i]
$to=$arr.to[$i]
if(!(test-path $from -ErrorAction SilentlyContinue)){
$sourcePathErrors+="$from`n"
}
if(!(test-path $to -ErrorAction SilentlyContinue)){
$destinationPathErrors+="$to`n"
}
}

if($sourcePathErrors -or $destinationPathErrors){
if($sourcePathErrors){$sourcePathErrors};
if($destinationPathErrors){$destinationPathErrors};
"Path errors:`n$sourcePathErrors`n$destinationPathErrors";
}else{
"All paths appear to be valid.";
}

# Write log header
$initInfo="=============================================Job Started: $dateStamp=============================================`r`n";
$initInfo+="Powershell version detected: $($PSVersionTable.PSVersion.Major)`.$($PSVersionTable.PSVersion.Minor)`r`n"
$initInfo+="Processing the following operations:`n";
if($arr.from -is [Array]){
$initInfo+=for ($i=0;$i -lt $arr.from.length; $i++){
"$($arr.from[$i]) => $($arr.to[$i])`n";
}
}else{$initInfo+="$($arr.from) => $($arr.to)`n";}
$initInfo;
Add-Content $logFile $initInfo;

# This function is useful for sanity checks prior to assigning persistent drive letters
function selectPsDrive{
# Select available drive letter at this moment in time
$driveLettersExclusion="[CDHKLMPZ]"
$availableDriveLetters=ls function:[A-Z]: -n|?{!(test-path $_)}|%{$_[0]}|?{!($_ -match $driveLettersExclusion)}
$GLOBAL:firstAvailableDriveLetter=$availableDriveLetters[0];
if ($firstAvailableDriveLetter -in (Get-PSdrive).Name){return $False;}else{return $True;}
}

function clearAllMappedDrives{
# The old-school method
Net Use * /delete /y

# The fancy new Powow Shill method
# Get a list of currently mapped drives
$mappedDrives = Get-WMIObject Win32_LogicalDisk | Where-Object { $_.DriveType -eq 4 }

if ($mappedDrives) {
$driveList = $mappedDrives.DeviceID
Foreach ($drive in $driveList) {
$driveLetter = $drive -replace ":"
Remove-SmbMapping -LocalPath $Drive -Force -UpdateProfile
If ( (Get-PSDrive -Name $driveLetter) 2>$Null ) {
Remove-PSDrive -Name $driveLetter -Force
}
}
}
}

function copyFilesFromDifferentDomain{
param(
[string]$from,
[string]$to
)

if (selectPsDrive){
<# Troubleshooting: How to remove persistent and hidden PSDrive
PS C:\Windows\system32> New-PSDrive -Name $firstAvailableDriveLetter -PSProvider FileSystem -Root $from -Persist -Credential $sourceCredential
New-PSDrive : The local device name has a remembered connection to another network resource
At line:1 char:1
+ New-PSDrive -Name $firstAvailableDriveLetter -PSProvider FileSystem - ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (A:PSDriveInfo) [New-PSDrive], Win32Exception
+ FullyQualifiedErrorId : CouldNotMapNetworkDrive,Microsoft.PowerShell.Commands.NewPSDriveCommand

PS C:\Windows\system32> net use
New connections will be remembered.
Status Local Remote Network

-------------------------------------------------------------------------------
OK \\FS01.KIMCONNECT.COM\IPC$ Microsoft Windows Network
The command completed successfully.

PS C:\Windows\system32> net use /delete \\FS01.KIMCONNECT.COM\IPC$
\\FS01.HULU.COM\IPC$ was deleted successfully.

The incident above has correlated to the "persistent" setting of the New-PSDrive command that was triggered prior to be followed by Remove-PSDrive
Persitent: New-PSDrive -Name $firstAvailableDriveLetter -PSProvider FileSystem -Root $from -Persistent -Credential $sourceCredential

Failed experimental method
Create a UNC map to the outside-domain source
Test: New-PSDrive -Name "mapped_by_$($env:username)" -PSProvider FileSystem -Root "\\fs01.HULU.com\intellisense_home\00-Intellisense_Systems" -Credential $sourceCredential
$mapName=firstAvailableDriveLetter;
if ($mapName -in (Get-PSDrive).Name){Remove-PSDrive -Name $mapName -Scope Global|Out-Null}
New-PSDrive -Name $mapName -PSProvider FileSystem -Root $from -Credential $sourceCredential
#>
if ($firstAvailableDriveLetter -in (Get-PSDrive).Name){Remove-PSDrive -Name $firstAvailableDriveLetter -Scope Global}
try{
Invoke-Expression "net use '$firstAvailableDriveLetter`:' $from /user:$sourceDomainUser $plainTextPassword /y"
#New-PSDrive -Name $firstAvailableDriveLetter -PSProvider FileSystem -Root $from -Credential $sourceCredential|Out-Null
}
catch{
clearAllMappedDrives;
Invoke-Expression "net use '$firstAvailableDriveLetter`:' $from /user:$sourceDomainUser $plainTextPassword /y"
#New-PSDrive -Name $firstAvailableDriveLetter -PSProvider FileSystem -Root $from -Credential $sourceCredential|Out-Null
}

# Validate the source path prior to copying
if(test-path $from -ErrorAction SilentlyContinue){
try{
write-host "robocopy '$from' '$to' $switches $log"
invoke-expression "robocopy '$from' '$to' $switches $log"
}
catch{
$errorMessage = $_.Exception.Message
$failedItem = $_.Exception.ItemName
write-Host "$errorMessage $failedItem"
continue;
}
}else{
write-Host "$from is not accessible."
$GLOBAL:pathErrors+="$from`n"
}

# Release UNC map for future reassignments
# Failed experimental method: Remove-PSDrive -Name $mapName -Scope Global -Force
# Remove-PSDrive -Name $firstAvailableDriveLetter -Scope Global -Force
Invoke-Expression "net use /delete '$firstAvailableDriveLetter`:' /y"
}
}

# Process the copying operations depending on whether we have a multi-dimensional array
if($arr.from -is [Array]){
# Create paths to Sources and trigger robocopy for each item in the array
$arrayLength=$arr.from.length
for ($i=0; $i -lt $arrayLength; $i++){
$from=$arr.from[$i]
$to=$arr.to[$i]
$processDisplay="=============Pass $($i+1) of $arrayLength`: $from => $to=======================================`r";
Write-Host $processDisplay;
Add-Content $logFile $processDisplay;
copyFilesFromDifferentDomain -from $from -to $to;
$passMarker="==============Pass $($i+1) of $arrayLength` Completed=======================================`r";
Add-Content $logFile $passMarker;
}
}else{
$processDisplay="=======================================Pass 1 of 1: $($arr.from) => $($arr.to)=======================================`r";
Write-Host $processDisplay;
Add-Content $logFile $processDisplay;
copyFilesFromDifferentDomain -from $arr.from -to $arr.to;
$passMarker="==============Pass $($i+1) of $arrayLength` Completed=======================================`r";
Add-Content $logFile $passMarker;
}

if ($pathErrors){Add-Content $logFile "Path errors:`n$pathErrors";}

# Stop the timer
$time=[math]::Round($($stopWatch.Elapsed.TotalHours),2);

# Write closure to log
Write-Host "Copying process completed in $time hours."
Add-Content $logFile $pathErrors
Add-Content $logFile "`n---------------------------------Copying process completed in $time hours---------------------------------"

Leave a Reply

Your email address will not be published. Required fields are marked *