当前位置:网站首页 > 更多 > 玩电脑 > 正文

[玩转系统] 创建 PowerShell 备份系统第 2 部分

作者:精品下载站 日期:2024-12-14 07:55:51 浏览:18 分类:玩电脑

创建 PowerShell 备份系统第 2 部分


昨天我开始撰写一系列文章来记录我的基于 PowerShell 的备份系统。我的系统的核心是使用 System.IO.FileSystemWatcher 作为跟踪每日文件更改的手段,以便我知道要备份哪些内容。然而,也存在一些挑战。我需要查看多个文件夹,我需要有一种简单的方法来知道要备份什么,并且它需要自动发生,而无需我付出任何努力,例如启动 PowerShell 和启动进程。我的解决方案是利用 PowerShell 计划作业。

创建 PowerShell 计划作业

PowerShell 计划作业利用 Windows 任务计划程序来运行 PowerShell 脚本块或脚本。您可以轻松使用任务计划程序并受益或运行 PowerShell 代码。虽然从控制台创建计划作业非常简单,但我倾向于将命令保存在脚本文件中。这样做可以为我提供文档跟踪,并使重新创建作业变得更容易。虽然可以编辑现有作业,但我发现简单地删除它并重新创建它更容易。使用脚本文件使这个过程变得简单。

我的预定工作需要执行一个操作。出于备份目的,这意味着创建一个 FileSystemWatcher 来监视每个目录和相应的事件订阅。每个事件都需要能够记录文件更改,以便我知道需要增量备份哪些内容。首先,我创建了一个文本文件,其中包含要监视的路径。

#comment out paths with a # symbol at the beginning of the line

C:\Scripts
C:\users\jeff\documents
D:\jdhit
C:\users\jeff\dropbox
C:\Users\Jeff\Google Drive

这是 C:\Scripts\myBackupPaths.txt。在我计划的作业操作中,我解析此文件以获取路径列表。

if (Test-Path c:\scripts\myBackupPaths.txt) {
    #filter out commented lines and lines with just white space
    $paths = Get-Content c:\scripts\myBackupPaths.txt | Where-Object {$_ -match "(^[^#]\S*)" -and $_ -notmatch "^\s+$"}
  }
  else {
    Throw "Failed to find c:\scripts\myBackupPaths.txt"
    #bail out
    Return
  }

我添加了一些过滤来去除空行和注释行。对于每条路径,我将创建一个观察者和事件订阅。正如我稍后将向您展示的那样,当事件触发时,意味着文件已更改,我打算将其记录到 CSV 文件中。每个事件订阅的操作将是运行 PowerShell 脚本文件。

Foreach ($Path in $Paths.Trim()) {

    #get the directory name from the list of paths
    $name = ((Split-Path $path -Leaf).replace(' ', ''))

    #specify the directory for the CSV log files
    $log = "D:\Backup\{0}-log.csv" -f $name
    
    #define the watcher object
    Write-Host "Creating a FileSystemWatcher for $Path" -ForegroundColor green
    $watcher = [System.IO.FileSystemWatcher]($path)
    $watcher.IncludeSubdirectories = $True
    #enable the watcher
    $watcher.EnableRaisingEvents = $True

    #the Action scriptblock to be run when an event fires
    $sbtext = "c:\scripts\LogBackupEntry.ps1 -event `$event -CSVPath $log"

    $sb = [scriptblock]::Create($sbtext)

日志文件使用每个文件夹路径的最后部分。如果名称有空格,例如“Google Drive”,我会替换该空格,使名称变为“GoogleDrive”。因此,我最终会得到几个 CSV 文件,例如 D:\Backup\Scripts-log.csv。

记录文件

脚本块文本使用变量扩展,以便 $log 将替换为实际的日志名称。我正在转义 $event,以便文本将其保留为 $符号。当我创建脚本块时,它看起来像

C:\scripts\LogBackupEntry.ps1 -event $event D:\Backup\Scripts-log.csv

$event 将是触发的事件对象。这是您从 Get-Event 获得的对象。该事件如果传递给此脚本。

#requires -version 5.1

[cmdletbinding()]
Param(
  [Parameter(Mandatory)]
  [object]$Event,
  [Parameter(Mandatory)]
  [string]$CSVPath
)

#uncomment for debugging and testing
# this will create a serialized version of each fired event
# $event | export-clixml ([System.IO.Path]::GetTempFileName()).replace("tmp","xml")

if (Test-Path $event.SourceEventArgs.fullpath) {

  $f = Get-Item -path $event.SourceEventArgs.fullpath -force

  #only save files and not a temp file
  if ((-Not $f.psiscontainer) -AND ($f.basename -notmatch "(^(~|__rar).*)|(.*\.tmp$)")) {

    [pscustomobject]@{
      ID        = $event.EventIdentifier
      Date      = $event.timeGenerated
      Name      = $event.sourceEventArgs.Name
      IsFolder  = $f.PSisContainer
      Directory = $f.DirectoryName
      Size      = $f.length
      Path      = $event.sourceEventArgs.FullPath
    } | Export-Csv -NoTypeInformation -Path $CSVPath -Append

  } #if not a container

} #if test-path

#end of script

脚本文件最终会将自定义对象导出到适当的 CSV 文件。但是,由于有时文件可能在检测到后已被删除,例如临时文件,因此我使用 Test-Path 来验证该文件是否仍然存在。如果是,我会获取该文件,然后进行一些额外的测试和过滤,以仅导出文件,并且只有在它们不是临时文件时才导出。例如,当您使用 PowerPoint 时,您将获得许多临时文件,这些文件将一直存在,直到您关闭应用程序。此时文件将被删除。我不想将它们记录到 CSV 文件中。

关于 PowerPoint 的有趣旁注 - 实际的 pptx 文件从未被检测到已更改。换句话说,当我编辑 PowerPoint 演示文稿时,该文件永远不会被记录。但这没关系,因为我始终可以使用其他文件手动更新 CSV 文件。

Function Add-BackupEntry {
    [cmdletbinding(SupportsShouldProcess)]
    Param(
        [Parameter(Position = 0, Mandatory, ValueFromPipeline)]
        [string]$Path,
        [Parameter(Mandatory)]
        [ValidateScript( { Test-Path $_ })]
        [string]$CSVFile
    )

    Begin {
        Write-Verbose "[BEGIN  ] Starting: $($MyInvocation.Mycommand)"
        $add = @()
    } #begin

    Process {
        Write-Verbose "[PROCESS] Adding: $Path"

        $file = Get-Item $Path
        $add += [pscustomobject]@{
            ID        = 99999
            Date      = $file.LastWriteTime
            Name      = $file.name
            IsFolder  = "False"
            Directory = $file.Directory
            Size      = $file.length
            Path      = $file.FullName
        }
        
    } #process
    End {
        Write-Verbose "[END    ] Exporting to $CSVFile"
        $add | Export-Csv -Path $CSVFile -Append -NoTypeInformation
        Write-Verbose "[END    ] Ending: $($MyInvocation.Mycommand)"
    } #end
}

我可以随时检查 CSV 文件,了解要备份哪些文件。

[玩转系统] 创建 PowerShell 备份系统第 2 部分

FileSystemWatcher 通常会对同一个文件触发多次。我稍后再处理。

创建事件订阅

我仍然需要为每个文件夹和观察者创建事件订阅。

$params = @{
      InputObject      = $watcher
      Eventname        = "changed"
      SourceIdentifier = "FileChange-$Name"
      MessageData      = "A file was created or changed in $Path"
      Action           = $sb
    }

    $params.MessageData | Out-String | Write-Host -ForegroundColor cyan
    $params.Action | Out-String | Write-Host -ForegroundColor Cyan
    Register-ObjectEvent @params

请记住,所有这些都将作为 PowerShell 计划作业的一部分运行。换句话说,就是一个运行空间。为了使事件订阅者持续存在,运行空间必须保持运行。

Do {
    Start-Sleep -Seconds 1
  } while ($True)

也许不是最优雅的方法,但它确实有效。

注册 PowerShell 计划作业

最后一步是创建计划作业。因为我希望它自动持续运行,所以我创建了一个作业触发器来在启动时运行该作业。

$trigger = New-JobTrigger -AtStartup

Register-ScheduledJob -Name "DailyWatcher" -ScriptBlock $action -Trigger $trigger

我现在可以在任务计划程序中手动启动任务或从 PowerShell 启动它。

Get-ScheduledTask.ps1 DailyWatcher | Start-ScheduledTask

这是我完整的注册脚本。

#requires -version 5.1
#requires -module PSScheduledJob

#create filesystemwatcher job for my incremental backups.

#scheduled job scriptblock
$action = {

  if (Test-Path c:\scripts\myBackupPaths.txt) {
    #filter out commented lines and lines with just white space
    $paths = Get-Content c:\scripts\myBackupPaths.txt | Where-Object {$_ -match "(^[^#]\S*)" -and $_ -notmatch "^\s+$"}
  }
  else {
    Throw "Failed to find c:\scripts\myBackupPaths.txt"
    #bail out
    Return
  }

  #trim leading and trailing white spaces in each path
  Foreach ($Path in $Paths.Trim()) {

    #get the directory name from the list of paths
    $name = ((Split-Path $path -Leaf).replace(' ', ''))

    #specify the directory for the CSV log files
    $log = "D:\Backup\{0}-log.csv" -f $name
    
    #define the watcher object
    Write-Host "Creating a FileSystemWatcher for $Path" -ForegroundColor green
    $watcher = [System.IO.FileSystemWatcher]($path)
    $watcher.IncludeSubdirectories = $True
    #enable the watcher
    $watcher.EnableRaisingEvents = $True

    #the Action scriptblock to be run when an event fires
    $sbtext = "c:\scripts\LogBackupEntry.ps1 -event `$event -CSVPath $log"

    $sb = [scriptblock]::Create($sbtext)

    #register the event subscriber
    
    #possible events are Changed,Deleted,Created
    $params = @{
      InputObject      = $watcher
      Eventname        = "changed"
      SourceIdentifier = "FileChange-$Name"
      MessageData      = "A file was created or changed in $Path"
      Action           = $sb
    }

    $params.MessageData | Out-String | Write-Host -ForegroundColor cyan
    $params.Action | Out-String | Write-Host -ForegroundColor Cyan
    Register-ObjectEvent @params

  } #foreach path

  Get-EventSubscriber | Out-String | Write-Host -ForegroundColor yellow

  #keep the job alive
  Do {
    Start-Sleep -Seconds 1
  } while ($True)

} #close job action

$trigger = New-JobTrigger -AtStartup

Register-ScheduledJob -Name "DailyWatcher" -ScriptBlock $action -Trigger $trigger

下一步

此时,我有一个基本上在后台运行的 PowerShell 计划作业,监视文件夹中的文件更改并记录到 CSV 文件。下次我将向您介绍我如何使用这些数据。

您需要 登录账户 后才能发表评论

取消回复欢迎 发表评论:

关灯