【问题标题】:Comparing filehash and outputting files比较文件哈希和输出文件
【发布时间】:2019-02-01 22:53:19
【问题描述】:

我是 PowerShell 新手,正在编写一个脚本来获取目录的哈希并将其存储在 .txt 文件中。

然后我想将它与早期版本进行比较并检查更改。如果有更改,我想要一个新的 .txt 或 .html 文件,其中包含哪些行项目已更改,以及最后修改日期。

到目前为止,我已经完成了比较工作,并且基于通过/失败的结果步骤工作正常。

我需要帮助的是将结果输出到一个 .txt 文件中,该文件仅列出已更改的文件,包括算法、哈希、文件名、上次编辑时间等字段。我知道我可以使用

(Get-Item $source).LastWriteTime

要获取写入时间,但我需要为目录中的每个文件执行此操作,而不仅仅是包含哈希的 .txt 文件。

# Variables
$Hashstore = "d:\baseline.txt"
$HashCompare = "d:\hashcompare.txt"
$HashTemp = "d:\hashtemp.txt"
$FileDir = "d:\New2"
$DateTime = Get-Date -format M.d.yyyy.hh.mm.ss

# Email Variables
$smtp_server = '<yourSMTPServer>'
$to_email = '<email>'
$from_email = '<email>'
$dns_server = "<yourExternalDNSServer>"
$domain = "<yourDomain>"

# Check if Baseline.txt Exists
If (Test-Path $Hashstore)
    # // File exists
{}
Else {
    # // File does not exist - Should never happen!
    $RefreshHash = dir $FileDir | Get-FileHash -Algorithm MD5
    $RefreshHash | Out-File $Hashstore
}

# Generate new Compare Hash.txt
$HashNew = dir $FileDir -Recurse | Get-FileHash -Algorithm MD5
$HashNew | Out-File $HashCompare

# Get Hash of baseline.txt
$HashBaseline = Get-FileHash -Path d:\baseline.txt -Algorithm MD5

#Get Hash of hashcompare.txt
$HashDiff = Get-FileHash -Path d:\hashcompare.txt -Algorithm MD5

#If changed, output hash to storage, and flag changes
If ($HashBaseline.hash -eq $HashDiff.hash)
{
    Add-Content -Path d:\success.$DateTime.txt -Value " Source Files ARE EQUAL </p>"
}
else
{
    Add-Content -Path d:\failure.$DateTime.html -Value "Source Files NOT EQUAL </p>"
    $HashNew | Out-File $HashTemp
}

# Compare two logs, send email if there is a change

If ($diff_results)
{
    #$evt_message = Get-Content .\domain.new.txt | Out-String
    #Write-EventLog -LogName Application -EventId 9000 -EntryType Error -Source "Maximo Validation Script" -Message $evt_message
    #Send-MailMessage -To $to_email -From $from_email -SmtpServer $smtp_server -Attachments .\domain.new.txt -Subject "ALERT! Change in Records" -Body "A change has been detected in the Maximo system files.`n`n`tACTION REQUIRED!`n`nVerify that this change was authorized."
}

If ($HashNew.HashString -eq $Hashstore.HashString)
{
}
else
{
    $HashTemp | Out-File $HashStore
}

我知道添加项可能不是写入我正在创建的此日志的最佳方式。将最后写入时间添加到每个读取的文件的最佳方法是什么?

【问题讨论】:

    标签: powershell hash


    【解决方案1】:

    这是为每个已更改的文件输出所需信息(算法、哈希、文件名、上次编辑时间)的简洁方法:

    $Hashstore = "d:\baseline.txt"
    $HashCompare = "d:\hashcompare.txt"
    $HashTemp = "d:\hashtemp.txt"
    $FileDir = "d:\New2"
    $DateTime = Get-Date -format M.d.yyyy.hh.mm.ss
    
        # Check if Baseline.txt Exists
    If (Test-Path $Hashstore)
    # // File exists
        {
    }
    Else {
         # // File does not exist - Should never happen!
         $RefreshHash = dir $FileDir -Recurse | Get-FileHash -Algorithm MD5
         $RefreshHash | Export-Csv -Path $Hashstore -NoTypeInformation -Force
    }
    
    # Generate new Compare Hash.txt
    $HashNew = dir $FileDir -Recurse | Get-FileHash -Algorithm MD5
    $HashNew | Export-Csv -Path $HashCompare -NoTypeInformation -Force
    
    # Get Hash of baseline.txt
    $HashBaseline = Get-FileHash -Path $Hashstore -Algorithm MD5
    
    #Get Hash of hashcompare.txt
    $HashDiff = Get-FileHash -Path $HashCompare -Algorithm MD5
    
    #If changed, output hash to storage, and flag changes
    If ($HashBaseline.hash -eq $HashDiff.hash) {
        Add-Content -Path D:\success.$DateTime.txt -Value " Source Files ARE EQUAL </p>"
    }
    Else {
        Add-Content -Path D:\failure.$DateTime.txt -Value "Source Files NOT EQUAL </p>"
        $HashNew | Export-Csv -Path $HashTemp -NoTypeInformation -Force
    
        # Storing a collection of differences in $Diffs
        $Diffs = Compare-Object -ReferenceObject (Import-Csv $Hashstore) -DifferenceObject (Import-Csv $HashCompare)
    
        Foreach ($Diff in $Diffs) {
            $DiffHashInfo = $Diff | Select-Object -ExpandProperty InputObject
            $DiffFileInfo =  Get-ChildItem -Path $DiffHashInfo.Path
    
            # Creating a list of properties for the information you need
            $DiffObjProperties = [ordered]@{'Algorithm'=$DiffHashInfo.Algorithm
                                   'Hash'=$DiffHashInfo.Hash
                                   'Filename'=$DiffFileInfo.Name
                                   'Last edit time'=$DiffFileInfo.LastWriteTime
                                   }
    
            # Building a custom object from the list of properties in $DiffObjProperties
            $DiffObj = New-Object -TypeName psobject -Property $DiffObjProperties
            $DiffObj
        }
    }
    

    在创建文件 $Hashstore 和 $HashCompare 之前,我将它们包含的信息转换为 CSV 格式,而不是纯文本。

    使用 Import-CSV 使他们的内容更容易在以后进行操作。 这使得适当的对象具有我可以使用的属性。 这也使它们更易于比较,并且这种比较的结果 ($Diffs) 是这些适当对象的集合。

    所以 $Diffs 包含所有已更改的文件,我在 Foreach 语句中遍历每个文件。

    这允许您为每个已更改的文件创建一个自定义对象 ($DiffObj),其中包含您需要的信息 ($DiffObjProperties)。

    【讨论】:

    • 顺便说一下,这些文件仍然使用 .txt 扩展名,尽管它们包含 CSV 格式的信息。没关系。您可以将文件的扩展名更改为 .csv ,或者如果您愿意,也可以将它们保留为 .txt。
    • 马修,非常感谢你!我对 PS 很陌生,并且已经为此苦苦挣扎了好几天。有两件事可以使这正是我所需要的,如果我错了,你介意纠正我吗?我知道我可以使用“| export-CLIxml d:\file.xml”管道输出。当我这样做时,它会留下一堆我不需要的元数据,例如 Objs 版本、Obj RefID、TN RefID、System.Management.Automation.PCCustomObjectSystem.Object。有没有办法只留下 $DiffObjProperties 信息?另外,我该怎么做才能获得完整路径,而不仅仅是文件名?再次,非常感谢!
    • 要获得更干净的 XML 文件,您可以这样做:| ConvertTo-Xml -As String -NoTypeInformation |输出文件 -FilePath C:\file.xml 。但是,您以后将无法在 Import-Clixml 中使用它。
    • 另外,如果您想要更改的文件的完整路径而不是名称,只需将行:'Filename'=$DiffFileInfo.Name 替换为行:'Filename'=$DiffFileInfo 。全名 。或者 'FilePath'=$DiffFileInfo.FullName ,引号之间是您的自定义属性的名称,您可以随意调用它。
    • 这是完美的。非常感谢。
    【解决方案2】:

    使用 MD5 哈希的 PowerShell v3+ 递归目录差异

    我使用这个纯 PowerShell(无依赖关系)递归文件内容差异。它在内存中计算每个目录文件内容的 MD5 哈希(算法是可配置的),并以标准 PowerShell Compare-Object 格式给出结果。

    它可以选择导出为 CSV 文件以及摘要文本文件。它可以将 rdiff.ps1 文件放到您的路径中,也可以将内容复制到您的脚本中。

    USAGE: rdiff path/to/left,path/to/right [-s path/to/summary/dir]

    这里是gist。我复制了以下内容以供参考,但我建议使用 gist 版本,因为随着时间的推移我会添加新功能。

    #########################################################################
    ### USAGE: rdiff path/to/left,path/to/right [-s path/to/summary/dir]  ###
    ### ADD LOCATION OF THIS SCRIPT TO PATH                               ###
    #########################################################################
    [CmdletBinding()]
    param (
      [parameter(HelpMessage="Stores the execution working directory.")]
      [string]$ExecutionDirectory=$PWD,
    
      [parameter(Position=0,HelpMessage="Compare two directories recursively for differences.")]
      [alias("c")]
      [string[]]$Compare,
    
      [parameter(HelpMessage="Export a summary to path.")]
      [alias("s")]
      [string]$ExportSummary
    )
    
    ### FUNCTION DEFINITIONS ###
    
    # SETS WORKING DIRECTORY FOR .NET #
    function SetWorkDir($PathName, $TestPath) {
      $AbsPath = NormalizePath $PathName $TestPath
      Set-Location $AbsPath
      [System.IO.Directory]::SetCurrentDirectory($AbsPath)
    }
    
    # RESTORES THE EXECUTION WORKING DIRECTORY AND EXITS #
    function SafeExit() {
      SetWorkDir /path/to/execution/directory $ExecutionDirectory
      Exit
    }
    
    function Print {
      [CmdletBinding()]
      param (
        [parameter(Mandatory=$TRUE,Position=0,HelpMessage="Message to print.")]
        [string]$Message,
    
        [parameter(HelpMessage="Specifies a success.")]
        [alias("s")]
        [switch]$SuccessFlag,
    
        [parameter(HelpMessage="Specifies a warning.")]
        [alias("w")]
        [switch]$WarningFlag,
    
        [parameter(HelpMessage="Specifies an error.")]
        [alias("e")]
        [switch]$ErrorFlag,
    
        [parameter(HelpMessage="Specifies a fatal error.")]
        [alias("f")]
        [switch]$FatalFlag,
    
        [parameter(HelpMessage="Specifies a info message.")]
        [alias("i")]
        [switch]$InfoFlag = !$SuccessFlag -and !$WarningFlag -and !$ErrorFlag -and !$FatalFlag,
    
        [parameter(HelpMessage="Specifies blank lines to print before.")]
        [alias("b")]
        [int]$LinesBefore=0,
    
        [parameter(HelpMessage="Specifies blank lines to print after.")]
        [alias("a")]
        [int]$LinesAfter=0,
    
        [parameter(HelpMessage="Specifies if program should exit.")]
        [alias("x")]
        [switch]$ExitAfter
      )
      PROCESS {
        if($LinesBefore -ne 0) {
          foreach($i in 0..$LinesBefore) { Write-Host "" }
        }
        if($InfoFlag) { Write-Host "$Message" }
        if($SuccessFlag) { Write-Host "$Message" -ForegroundColor "Green" }
        if($WarningFlag) { Write-Host "$Message" -ForegroundColor "Orange" }
        if($ErrorFlag) { Write-Host "$Message" -ForegroundColor "Red" }
        if($FatalFlag) { Write-Host "$Message" -ForegroundColor "Red" -BackgroundColor "Black" }
        if($LinesAfter -ne 0) {
          foreach($i in 0..$LinesAfter) { Write-Host "" }
        }
        if($ExitAfter) { SafeExit }
      }
    }
    
    # VALIDATES STRING MIGHT BE A PATH #
    function ValidatePath($PathName, $TestPath) {
      If([string]::IsNullOrWhiteSpace($TestPath)) {
        Print -x -f "$PathName is not a path"
      }
    }
    
    # NORMALIZES RELATIVE OR ABSOLUTE PATH TO ABSOLUTE PATH #
    function NormalizePath($PathName, $TestPath) {
      ValidatePath "$PathName" "$TestPath"
      $TestPath = [System.IO.Path]::Combine((pwd).Path, $TestPath)
      $NormalizedPath = [System.IO.Path]::GetFullPath($TestPath)
      return $NormalizedPath
    }
    
    
    # VALIDATES STRING MIGHT BE A PATH AND RETURNS ABSOLUTE PATH #
    function ResolvePath($PathName, $TestPath) {
      ValidatePath "$PathName" "$TestPath"
      $ResolvedPath = NormalizePath $PathName $TestPath
      return $ResolvedPath
    }
    
    # VALIDATES STRING RESOLVES TO A PATH AND RETURNS ABSOLUTE PATH #
    function RequirePath($PathName, $TestPath, $PathType) {
      ValidatePath $PathName $TestPath
      If(!(Test-Path $TestPath -PathType $PathType)) {
        Print -x -f "$PathName ($TestPath) does not exist as a $PathType"
      }
      $ResolvedPath = Resolve-Path $TestPath
      return $ResolvedPath
    }
    
    # Like mkdir -p -> creates a directory recursively if it doesn't exist #
    function MakeDirP {
      [CmdletBinding()]
      param (
        [parameter(Mandatory=$TRUE,Position=0,HelpMessage="Path create.")]
        [string]$Path
      )
      PROCESS {
        New-Item -path $Path -itemtype Directory -force | Out-Null
      }
    }
    
    # GETS ALL FILES IN A PATH RECURSIVELY #
    function GetFiles {
      [CmdletBinding()]
      param (
        [parameter(Mandatory=$TRUE,Position=0,HelpMessage="Path to get files for.")]
        [string]$Path
      )
      PROCESS {
        ls $Path -r | where { !$_.PSIsContainer }
      }
    }
    
    # GETS ALL FILES WITH CALCULATED HASH PROPERTY RELATIVE TO A ROOT DIRECTORY RECURSIVELY #
    # RETURNS LIST OF @{RelativePath, Hash, FullName}
    function GetFilesWithHash {
      [CmdletBinding()]
      param (
        [parameter(Mandatory=$TRUE,Position=0,HelpMessage="Path to get directories for.")]
        [string]$Path,
    
        [parameter(HelpMessage="The hash algorithm to use.")]
        [string]$Algorithm="MD5"
      )
      PROCESS {
        $OriginalPath = $PWD
        SetWorkDir path/to/diff $Path
        GetFiles $Path | select @{N="RelativePath";E={$_.FullName | Resolve-Path -Relative}},
                                @{N="Hash";E={(Get-FileHash $_.FullName -Algorithm $Algorithm | select Hash).Hash}},
                                FullName
        SetWorkDir path/to/original $OriginalPath
      }
    }
    
    # COMPARE TWO DIRECTORIES RECURSIVELY #
    # RETURNS LIST OF @{RelativePath, Hash, FullName}
    function DiffDirectories {
      [CmdletBinding()]
      param (
        [parameter(Mandatory=$TRUE,Position=0,HelpMessage="Directory to compare left.")]
        [alias("l")]
        [string]$LeftPath,
    
        [parameter(Mandatory=$TRUE,Position=1,HelpMessage="Directory to compare right.")]
        [alias("r")]
        [string]$RightPath
      )
      PROCESS {
        $LeftHash = GetFilesWithHash $LeftPath
        $RightHash = GetFilesWithHash $RightPath
        diff -ReferenceObject $LeftHash -DifferenceObject $RightHash -Property RelativePath,Hash
      }
    }
    
    ### END FUNCTION DEFINITIONS ###
    
    ### PROGRAM LOGIC ###
    
    if($Compare.length -ne 2) {
      Print -x "Compare requires passing exactly 2 path parameters separated by comma, you passed $($Compare.length)." -f
    }
    Print "Comparing $($Compare[0]) to $($Compare[1])..." -a 1
    $LeftPath   = RequirePath path/to/left $Compare[0] container
    $RightPath  = RequirePath path/to/right $Compare[1] container
    $Diff       = DiffDirectories $LeftPath $RightPath
    $LeftDiff   = $Diff | where {$_.SideIndicator -eq "<="} | select RelativePath,Hash
    $RightDiff   = $Diff | where {$_.SideIndicator -eq "=>"} | select RelativePath,Hash
    if($ExportSummary) {
      $ExportSummary = ResolvePath path/to/summary/dir $ExportSummary
      MakeDirP $ExportSummary
      $SummaryPath = Join-Path $ExportSummary summary.txt
      $LeftCsvPath = Join-Path $ExportSummary left.csv
      $RightCsvPath = Join-Path $ExportSummary right.csv
    
      $LeftMeasure = $LeftDiff | measure
      $RightMeasure = $RightDiff | measure
    
      "== DIFF SUMMARY ==" > $SummaryPath
      "" >> $SummaryPath
      "-- DIRECTORIES --" >> $SummaryPath
      "`tLEFT -> $LeftPath" >> $SummaryPath
      "`tRIGHT -> $RightPath" >> $SummaryPath
      "" >> $SummaryPath
      "-- DIFF COUNT --" >> $SummaryPath
      "`tLEFT -> $($LeftMeasure.Count)" >> $SummaryPath
      "`tRIGHT -> $($RightMeasure.Count)" >> $SummaryPath
      "" >> $SummaryPath
      $Diff | Format-Table >> $SummaryPath
    
      $LeftDiff | Export-Csv $LeftCsvPath -f
      $RightDiff | Export-Csv $RightCsvPath -f
    }
    $Diff
    SafeExit
    

    【讨论】:

      【解决方案3】:

      另一个我的版本。但没有日期/时间。

      # Check images. Display if differ
      #
      
      $file_path = "C:\Files"
      $last_state = "last_state.json"
      
      # Check last_state.json. If false - create new empty file.
      If (!(Test-Path $last_state)) {
          New-Item $last_state -ItemType file | Out-Null
      }
      $last_state_obj = Get-Content $last_state | ConvertFrom-Json
      
      # Get files list and hash. Also you can use -Recurse option
      Get-ChildItem $file_path -Filter *.* | 
      Foreach-Object {
          if (!$_.PSIsContainer) {
              $current_state += @($_ | Get-FileHash -Algorithm MD5)
          }
      }
      
      # Compare  hash
      ForEach ($current_file in $current_state) {
          if (($last_state_obj | where {$current_file.Path -eq $_.Path}).Hash -ne $current_file.Hash) {
              $changed += @($current_file)
          }
      }
      
      # Display changed files
      $changed
      
      # Save new hash to last_state.json
      $current_state | ConvertTo-JSON | Out-File $last_state
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-03-04
        • 2011-12-02
        • 2016-08-20
        • 1970-01-01
        • 2019-09-15
        • 1970-01-01
        • 2017-03-25
        • 1970-01-01
        相关资源
        最近更新 更多