Backup.psm1
#requires -Version 3.0 -Modules CimCmdlets, core <# Other requiremens: This module also requires that you have Microsoft's Sync Framework 2.0 & SDK installed. #> #region Volume Shadow Services Function Mount-VSSAllShadow { Param ( [Parameter(Mandatory=$true, HelpMessage='Destination directory')] [ValidateScript({ Test-Path -Path $_ -PathType Container } )] [String]$Path ) Get-CimInstance -ClassName Win32_ShadowCopy | Mount-VolumeShadowCopy -Path $Path -Verbose } Function Get-VSSShadow { vssadmin list shadows | Select-String -Pattern 'shadow copies at creation time' -Context 0,3 | ForEach-Object { [pscustomobject]@{ Path = (($_.Context.PostContext -split "\r\n")[2] -split ':')[1].Trim(); InstallDate = ($_.Line -split ':\s',2)[1]; } } } Function Mount-VolumeShadowCopy { <# .SYNOPSIS Mount a volume shadow copy. .DESCRIPTION Mount a volume shadow copy. .PARAMETER ShadowPath Path of volume shadow copies submitted as an array of strings .PARAMETER Destination Target folder that will contain mounted volume shadow copies .EXAMPLE Get-CimInstance -ClassName Win32_ShadowCopy | Mount-VolumeShadowCopy -Path C:\VSS -Verbose #> [CmdletBinding()] Param( [Parameter(Mandatory,ValueFromPipeline,ValueFromPipelineByPropertyName)] [ValidatePattern('\\\\\?\\GLOBALROOT\\Device\\HarddiskVolumeShadowCopy\d{1,}')] [Alias('DeviceObject')] [String[]]$ShadowPath, [Parameter(Mandatory=$true, HelpMessage='Destination directory')] [ValidateScript({ Test-Path -Path $_ -PathType Container } )] [String]$Path ) Begin { $typDef = @' using System; using System.Runtime.InteropServices; namespace mklink { public class symlink { [DllImport("kernel32.dll")] public static extern bool CreateSymbolicLink(string lpSymlinkFileName, string lpTargetFileName, int dwFlags); } } '@ Try { $null = [mklink.symlink] } Catch { Add-Type -TypeDefinition $typDef } } Process { $ShadowPath | ForEach-Object -Process { if ($($_).EndsWith('\')) { $sPath = $_ } else { $sPath = ('{0}\' -f ($_)) } $tPath = Join-Path -Path $Path -ChildPath ( '{0}-{1}' -f (Split-Path -Path $sPath -Leaf),[GUID]::NewGuid().Guid ) try { if ( [mklink.symlink]::CreateSymbolicLink($tPath,$sPath,1) ) { Write-Verbose -Message ('Successfully mounted {0} to {1}' -f $sPath, $tPath) } else { Write-Warning -Message ('Failed to mount {0}' -f $sPath) } } catch { Write-Warning -Message ('Failed to mount {0} because {1}' -f $sPath, $_.Exception.Message) } } } End {} } Function Dismount-VolumeShadowCopy { <# .SYNOPSIS Dismount a volume shadow copy. .DESCRIPTION Dismount a volume shadow copy. .PARAMETER Path Path of volume shadow copies mount points submitted as an array of strings .EXAMPLE Get-ChildItem -Path C:\VSS | Dismount-VolumeShadowCopy -Verbose #> [CmdletBinding()] Param( [Parameter(Mandatory,ValueFromPipeline,ValueFromPipelineByPropertyName)] [Alias('FullName')] [string[]]$Path ) Begin { } Process { $Path | ForEach-Object -Process { $sPath = $_ if (Test-Path -Path $sPath -PathType Container) { if ((Get-Item -Path $sPath).Attributes -band [System.IO.FileAttributes]::ReparsePoint) { try { [System.IO.Directory]::Delete($sPath,$false) | Out-Null Write-Verbose -Message ('Successfully dismounted {0}' -f $sPath) } catch { Write-Warning -Message ('Failed to dismount {0} because {1}' -f $sPath, $_.Exception.Message) } } else { Write-Warning -Message ("The path {0} isn't a reparsepoint" -f $sPath) } } else { Write-Warning -Message ("The path {0} isn't a directory" -f $sPath) } } } End {} } #endregion #region Synchronization Tools Function Sync-Directory { <# .SYNOPSIS Keep two directories synchronized .DESCRIPTION Built using the Microsoft Sync Framework 2.1. This function keeps two directories in sync with each other. Multiple clients can sync to the same shared directory. .EXAMPLE Sync-Directory -SourcePath 'C:\sourceDir' -DestinationPath 'C:\destinationDirectory' .EXAMPLE Sync-Directory '.\myImportantStuff' '\\ShareServer\myShare\importantStuff' -SyncHiddenFiles .REQUIREMENTS Microsoft Sync Framework 2.1 - https://www.microsoft.com/en-us/download/details.aspx?id=19502 #> <# Version 0.1 - Day one #> [CmdLetBinding()] Param ( [Parameter(Mandatory = $true, Position = 0)] [ValidateScript({ Test-Path $_ -PathType Container })] [String] $SourcePath, [ValidateScript({ try { [System.Guid]::Parse($_) | Out-Null $true } catch { $false } })] [String] $sGuid, [Parameter(Mandatory = $true, Position = 1)] [ValidateScript({ Test-Path $_ -PathType Container })] [String] $DestinationPath, [ValidateScript({ try { [System.Guid]::Parse($_) | Out-Null $true } catch { $false } })] [String] $dGuid, [String[]] $FileNameFilter = ('~*.tmp','*.dat','Desktop.ini','*.lnk','Thumbs.db','*.metadata'), [Switch] $SyncHiddenFiles, [Switch] $SyncSystemFiles, [ValidateScript({ Test-Path $_ -PathType Container })] [String] $ArchivePath ) Begin { # Debugging for scripts $Script:boolDebug = $PSBoundParameters.Debug.IsPresent # Includes $Libraries = ( 'Microsoft.Synchronization', 'Microsoft.Synchronization.Files', 'Microsoft.Synchronization.MetadataStorage' ) # Error action preference $ErrorActionPreference = 'Stop' Try { Foreach ($Library in $Libraries) { $null = [System.Reflection.Assembly]::LoadWithPartialName($Library) } } Catch { Write-Error -Message 'Failed to load Sync Framework libraries. Microsoft Sync Framework 2.1 required' } } Process { Function Script:Get-Match { <# .DESCRIPTION Matches an environmental variable used to store sync jobs. .PARAMETER InputObject Array of variables to filter. #> Param ( [Parameter(Mandatory=$true, ValueFromPipeline=$true, HelpMessage='Data to filter')] $InputObject ) Process { IF ($InputObject -match 'SyncDir_') { $InputObject } } } Function Script:Sync-File { Param ( [String] $Source, [GUID] $sGuid, [String] $Destination, [GUID] $dGuid, [Microsoft.Synchronization.Files.FileSyncScopeFilter] $Filter, [Microsoft.Synchronization.Files.FileSyncOptions] $Options ) $sourceProvider = $null $destinationProvider = $null Try { $sourceProvider = New-Object Microsoft.Synchronization.Files.FileSyncProvider ` -ArgumentList $sGuid, $Source, $Filter, $Options $destinationProvider = New-Object Microsoft.Synchronization.Files.FileSyncProvider ` -ArgumentList $dGuid, $Destination, $Filter, $Options # Agent and sync action $synDirection = [Microsoft.Synchronization.SyncDirectionOrder]::UploadAndDownload $syncAgent = [Microsoft.Synchronization.SyncOrchestrator]::new() [Microsoft.Synchronization.SyncProvider] $srcProv = $sourceProvider [Microsoft.Synchronization.SyncProvider] $dstProv = $destinationProvider $syncAgent.LocalProvider = $srcProv $syncAgent.RemoteProvider = $dstProv $syncAgent.Direction = $synDirection $results = $syncAgent.Synchronize() $results } Finally { If ($sourceProvider) { $sourceProvider.Dispose() } If ($destinationProvider) { $destinationProvider.Dispose() } } } Function Script:Get-Change { Param ( [String] $RootPath, [Guid] $Guid, [Microsoft.Synchronization.Files.FileSyncScopeFilter] $Filter, [Microsoft.Synchronization.Files.FileSyncOptions] $Options ) $Provider = $null Try { $Provider = New-Object Microsoft.Synchronization.Files.FileSyncProvider ` -ArgumentList $Guid, $RootPath, $Filter, $Options $Provider.DetectChanges() If ($boolDebug) { $Provider.GetChangeBatch() } } Finally { If ($Provider) { $Provider.Dispose() } } } # Guids #TODO: Need to get this from the MetaData file If ($sGuid) { $srcGuid = $sGuid } Else { $srcGuid = [guid]::NewGuid().guid } If ($dGuid) { $dstGuid = $dGuid } Else { $dstGuid = [guid]::NewGuid().guid } # Sync directories $strSourceDirectory = (Get-Item -Path $SourcePath).FullName -replace "\\$" $strDestinationDirectory = (Get-Item -Path $DestinationPath).FullName -replace "\\$" # Filter $scopeFilter = [Microsoft.Synchronization.Files.FileSyncScopeFilter]::new() # File attribute objects for the scope filter. We don't want hidden or system files $attribHidden = [System.IO.FileAttributes]::Hidden $attribSystem = [System.IO.FileAttributes]::System # Array needed cause there is no Add() method, only get or set; $arrayAttrib = ($attribHidden,$attribSystem) $scopeFilter.AttributeExcludeMask = $arrayAttrib $arrayNameFilters = $FileNameFilter Foreach ($nameFilter in $arrayNameFilters) { $scopeFilter.FileNameExcludes.Add("$nameFilter") } # Options object $syncOptions = ( [Microsoft.Synchronization.Files.FileSyncOptions]::RecycleConflictLoserFiles, [Microsoft.Synchronization.Files.FileSyncOptions]::RecycleDeletedFiles, [Microsoft.Synchronization.Files.FileSyncOptions]::RecyclePreviousFileOnUpdates ) # Detect all changes <# Get-Change -RootPath $strSourceDirectory -Filter $scopeFilter -Options $syncOptions -Guid $srcGuid Get-Change -RootPath $strDestinationDirectory -Filter $scopeFilter -Options $syncOptions -Guid $dstGuid #> # Sync files both directions Try { Sync-File -Source $strSourceDirectory -sGuid $srcGuid -Destination $strDestinationDirectory ` -dGuid $dstGuid -Filter $scopeFilter -Options $syncOptions } Catch { [String] $errorMessage = @' {0}, FAILURE!!, Something went wrong during the Sync-Files function Source directory: {1} Destination directory : {2} {3} '@ -f (Get-Date).ToString(), $strSourceDirectory, $strDestinationDirectory, "`n" $errorMessage | Out-File -FilePath "$PSScriptRoot\.Syncronization_error.log" -Encoding ascii -Append } } End { } } #endregion |