JohnL1953
JohnL1953

Reputation: 41

PowerShell, can't get LastWriteTime

I have this working, but need LastWriteTime and can't get it.

Get-ChildItem -Recurse | Select-String -Pattern "CYCLE" | Select-Object Path, Line, LastWriteTime

I get an empty column and zero Date-Time data

Upvotes: 4

Views: 1725

Answers (3)

Chris Chalmers
Chris Chalmers

Reputation: 1

In PowerShell, I was able to run the following line:

Get-ChildItem -Path $logPath |? {!$_.PSIsContainer -and $_.Extension -eq ".log"} | Foreach{$_.Refresh()}

Before the line I wanted to use the LastWriteTime in:

Get-ChildItem -Path $logPath | ? {!$_.PSIsContainer -and $_.Extension -eq ".log" `
                                        -and $_.LastWriteTime -gt (Get-Date).AddSeconds(-35)} `
                                        | Sort-Object -Property Name -Descending | Select-Object -First 1

Upvotes: 0

codewario
codewario

Reputation: 21418

LastWriteTime is a property of System.IO.FileSystemInfo, which is the base type of the items Get-ChildItem returns for the Filesystem provider (which is System.IO.FileInfo for files). Path and Line are properties of Microsoft.PowerShell.Commands.MatchInfo, which contains information about the match, not the file you passed in. Select-Object operates on the information piped into it, which comes from the previous expression in the pipeline, your Select-String in this case.

You can't do this as a (well-written) one-liner if you want the file name, line match, and the last write time of the actual file to be returned. I recommend using an intermediary PSCustomObject for this and we can loop over the found files and matches individually:

# Use -File to only get file objects
$foundMatchesInFiles = Get-ChildItem -Recurse -File | ForEach-Object {
  
  # Assign $PSItem/$_ to $file since we will need it in the second loop
  $file = $_

  # Run Select-String on each found file
  $file | Select-String -Pattern CYCLE | ForEach-Object {
    [PSCustomObject]@{
      Path = $_.Path
      Line = $_.Line
      FileLastWriteTime = $file.LastWriteTime
    }
  }
}

Note: I used a slightly altered name of FileLastWriteTime to exemplify that this comes from the returned file and not the match provided by Select-String, but you could use LastWriteTime if you wish to retain the original property name.

Now $foundMatchesInFiles will be a collection of files which have CYCLE occurring within them, the path of the file itself (as returned by Select-String), and the last write time of the file itself as was returned by the initial Get-ChildItem.


Additional considerations

You could also use Select-Object and computed properties but IMO the above is a more concise approach when merging properties from unrelated objects together. While not a poor approach, Select-Object outputs data with a type containing the original object type name (e.g. Selected.Microsoft.PowerShell.Commands.MatchInfo). The code may work fine but can cause some confusion when others who may consume this object in the future inspect the output members. LastWriteTime, for example, belongs to FileSystemInfo, not MatchInfo. Another developer may not understand where the property came from at first if it has the MatchInfo type referenced. It is generally a better design to create a new object with the merged properties.

That said this is a minor issue which largely comes down to stylistic preference and whether this object might be consumed by others aside from you. I write modules and scripts that many other teams in my organization consume so this is a concern for me. It may not be for you. @mklement0's answer is an excellent example of how to use computed properties with Select-Object to achieve the same functional result as this answer.

Upvotes: 3

mklement0
mklement0

Reputation: 437218

Select-String's output objects, which are of type Microsoft.PowerShell.Commands.MatchInfo, only contain the input file path (string), no other metadata such as LastWriteTime.

To obtain it, use a calculated property, combined with the common -PipelineVariable parameter, which allows you to reference the input file at hand in the calculated property's expression script block as a System.IO.FileInfo instance as output by Get-ChildItem, whose .LastWriteTime property value you can return:

Get-ChildItem -File -Recurse -PipelineVariable file | 
  Select-String -Pattern "CYCLE" | 
    Select-Object Path, 
                  Line, 
                  @{ 
                    Name='LastWriteTime'; 
                    Expression={ $file.LastWriteTime }
                  }

Note how the pipeline variable, $file, must be passed without the leading $ (i.e. as file) as the -PipelineVariable argument . -PipelineVariable can be abbreviated to -pv.

Upvotes: 4

Related Questions