[CmdletBinding(SupportsShouldProcess,ConfirmImpact='Medium')]param( [Parameter(Mandatory,ParameterSetName="RemoteSrc")] [string]$RemoteSrc, [Parameter(Mandatory,ParameterSetName="Local",Position=1)] [Parameter(Mandatory,ParameterSetName="RemoteSrc",Position=1)] [Parameter(Mandatory,ParameterSetName="RemoteDst",Position=1)] [string]$SrcBase, [Parameter(Mandatory,ParameterSetName="Local",Position=2)] [Parameter(Mandatory,ParameterSetName="RemoteSrc",Position=2)] [Parameter(Mandatory,ParameterSetName="RemoteDst",Position=2)] [string]$DstBase, [Parameter(Mandatory,ParameterSetName="RemoteDst")] [string]$RemoteDst, [Parameter(Position=4)] [string]$Path=".", [switch]$NoPassThru, [int]$Depth=-1, [int]$Limit, [Alias("Do")] [switch]$NoSimulate ) Write-Host -ForegroundColor Cyan ( $PSCmdlet | Out-String ) Write-Host -ForegroundColor Yellow ( $PSBoundParameters | Out-String ) function GetLocalPath{ [CmdletBinding(SupportsShouldProcess,ConfirmImpact='Low')]param( [string]$LocalBase, [string]$Path = "." ) return ([System.IO.Path]::GetFullPath((Join-Path $LocalBase $Path),"/")) } function GetRemotePath{ [CmdletBinding(SupportsShouldProcess,ConfirmImpact='Low')]param( [string]$RemoteAddr, [string]$RemoteBase, [string]$Path = "." ) return "$RemoteAddr`:$( [Management.Automation.WildcardPattern]::Escape( [System.IO.Path]::GetFullPath((Join-Path $RemoteBase $Path),"/") ) -replace '([&\[\(\)\\])','\$1' )" } function GetSubstituedPath{ [CmdletBinding(SupportsShouldProcess,ConfirmImpact='Low')]param( [string]$OriginalPath, [string]$Base, [string]$ReplaceBase, [string]$RemotePrefix ) $local:newPath = $OriginalPath -replace "^$([regex]::Escape($Base))",($ReplaceBase -replace '\$(\d)','\$$1') if( $RemotePrefix ) { $newPath = GetRemotePath $RemotePrefix $newPath } return $newPath } $local:SrcPath = GetLocalPath $SrcBase $Path $local:DstPath = GetLocalPath $DstBase $Path $local:LocalPath = [string]::Empty if( -not $RemoteSrc ) { $LocalPath = $SrcPath } else { $LocalPath = $DstPath } function GetSortedDirsDepthFirst { param([string]$Path,$StopDepth=-1,$CurrentDepth=0) $local:dirs = Get-ChildItem $Path -Directory | Sort-Object Name foreach( $local:d in $dirs ) { if( $StopDepth -lt 0 -or $CurrentDepth -lt $StopDepth ) { GetSortedDirsDepthFirst $d.FullName $StopDepth ($CurrentDepth + 1) } $d } } $local:limitParam = @{} if( $Limit -gt 0 ) { $limitParam.First = $Limit } $local:Pairs = @() if( -not $RemoteSrc ) { $Pairs = GetSortedDirsDepthFirst $LocalPath -StopDepth $Depth | Select-Object @limitParam | ForEach-Object { [PSCustomObject]([ordered]@{ SrcGB = -1 Src = GetLocalPath $_.FullName Dst = GetSubstituedPath $_.FullName $SrcPath $DstPath $RemoteDst }) } } else { $Pairs = , ([PSCustomObject]([ordered]@{ Src = GetRemotePath $RemoteSrc $SrcPath Dst = $DstPath })) } $local:sw = [System.Diagnostics.Stopwatch]::StartNew(); $Pairs | ForEach-Object { $local:s = $_.Src -replace "'",'${Q}' $local:d = $_.Dst -replace "'",'\\${Q}' $local:skip = $false if( $_.SrcGB ) { $_.SrcGB = [Math]::Round( ( Get-ChildItem -Recurse -File $_.Src | Measure-Object -Sum Length ).Sum / 1000000000, 2) if( -not $_.SrcGB ) { $_.SrcGB = "Skip" } else { $_.SrcGB = "$($_.SrcGB) GB" } } Write-Host -ForegroundColor Cyan ($($_ | Format-Table -HideTableHeaders | Out-String).Trim()) $local:exp = "Q=\`"'\`"; rsync -avzh --remove-source-files --progress \`"$s/\`" \`"$d/\`"" $sw.Restart() if( -not $_.SrcGB -or $_.SrcGB -ne "Skip" ) { $exp | Out-Host sh -c $exp | Out-Host } Write-Host -Cyan "Cleanup '$s'..." $exp = "Q=\`"'\`"; find \`"$s\`" -type d -empty -delete" sh -c $exp | Out-Host [PSCustomObject]([ordered]@{ PSTypeName = 'RsyncReport' SrcGB = $_.SrcGB Elapsed = $sw.Elapsed Src = $_.Src Dst = $_.Dst }) }