shirish
shirish

Reputation: 477

Is there a way to use the equivalent of touch in PowerShell to update timestamps of a file?

I use the Unix command touch a lot to update timestamps of any new acquired/downloaded file in my system. Many times the timestamp of the existing file is old, so it can get lost in the system. Is there a way in MS Windows PowerShell to do so. I have both PowerShell 5 as well as PowerShell 6.

Upvotes: 4

Views: 5575

Answers (2)

mklement0
mklement0

Reputation: 440431

Mark's helpful answer shows how to update a single file's last-modified timestamp.

Below is the source code for function Touch-File, which implements most of the functionality that the Unix touch utility offers in a PowerShell-idiomatic fashion, including support for -WhatIf, verbose output, and a pass-thru option.

It works in both Windows PowerShell (version 3 or higher) and PowerShell Core.

You can put the function in your $PROFILE, for instance;[1] call Get-Help Touch-File for help.
If you want to define an alias, I recommend against naming it touch, to prevent confusion with the native touch utility on Unix-like platforms;
Set-Alias tf Touch-File would work, for instance.

Examples:

# Sets the last-modified and last-accessed timestamps for all text files
# in the current directory to the current point in time.
Touch-File *.txt


# Creates files 'newfile1' and 'newfile2' and outputs information about them as 
# System.IO.FileInfo instances.
# Note the need to use "," to pass multiple paths.
Touch-File newfile1, newfile2 -PassThru


# Updates the last-modified and last-accessed timestamps for all text files
# in the current directory to midnight (the start of) of today's date.
Touch-File *.txt -DateTime (Get-Date).Date


# Sets the last-modified and last-accessed timestamps of all text files
# in the current directory back by 1 hour.
Get-Item *.txt | Touch-File -Offset '-1:0'


# Sets the last-modified and last-accessed timestamps of all files in the 
# current directory to the last-modified timestamp of the current directory.
Get-ChildItem -File | Touch-File -ReferencePath . '-0:1'

Touch-File source code:

Note:

  • The function below is also available as an MIT-licensed Gist, and only the latter will be maintained going forward. Assuming you have looked at the linked code to ensure that it is safe (which I can personally assure you of, but you should always check), you can install it directly as follows:

    irm https://gist.github.com/mklement0/82ed8e73bb1d17c5ff7b57d958db2872/raw/Touch-File.ps1 | iex
    
  • Touch is not an approved verb in PowerShell, but it was chosen nonetheless, because none of the approved verbs can adequately convey the core functionality of this command.

function Touch-File {
  <#
  .SYNOPSIS
  "Touches" files.

  .DESCRIPTION
  Similar to the Unix touch utility, this command updates the last-modified and
  last-accessed timestamps of files or creates files on
  demand.

  The current point in time is used by default, but you can pass a
  specific timestamp with -DateTime or use an existing file or directory's 
  last-modified timestamp with -ReferencePath.
  Alternatively, the target files' current timestamps can be adjusted with 
  a time span passed to -Offset.

  Symbolic links are invariably followed, which means that it is a file link's
  *target* whose last-modified timestamp get updated.
  Note: 
  * This means that a *link*'s timestamp is itself never updated.
  * If a link's target doesn't exist, a non-terminating error occurs.

  Use -WhatIf to preview the effects of a given command, and -Verbose to see
  details.
  Use -PassThru to pass the touched items through, i.e., to output updated
  information about them.

  Note that in order to pass multiple target files / patterns as arguments
  you must *comma*-separate them, because they bind to a single, array-valued
  parameter.

  .PARAMETER Path
  The paths of one or more target files, optionally expressed
  as wildcard expressions, and optionally passed via the pipeline.

  .PARAMETER LiteralPath
  The literal paths of one or more target files, optionally
  passed via the pipeline as output from Get-ChildItem or Get-Item.

  .PARAMETER DateTime
  The timestamp to assign as the last-modified (last-write) and last-access
  values.

  By default, the current point in time is used.

  .PARAMETER ReferencePath
  The literal path to an existing file or directory whose last-modified
  timestamp should be applied to the target file(s).

  .PARAMETER Offset
  A time span to apply as an offset to the target files' current last-write
  timestamp.

  Since the intent is to adust the current timestamps of *existing* files,
  non-existent paths are ignored; that is, -NoNew is implied.

  Note that positive values adjust the timestamps forward (to a more recent date),
  whereas negative values adjust backwards (to an earlier date.)

  Examples of strings that can be used to specify time spans:

  * '-1' adjust the current timestamp backward by 1 day
  * '-2:0' sets it backward by 2 hours 

  Alternatively, use something like -(New-TimeSpan -Days 1)

  .PARAMETER NoNew
  Specifies that only existing files should have their timestamps updated.

  By default, literal target paths that do not refer to existing items 
  result in files with these paths getting *created* on demand.

  A warning is issued for any non-existing input paths.

  .PARAMETER PassThru
  Specifies that the "touched" items are passed through, i.e. produced as this 
  command's output, as System.IO.FileInfo instances.

  .PARAMETER Force
  When wildcard expressions are passed to -Path, hidden files are matched too.

  .EXAMPLE
  Touch-File *.txt

  Sets the last-modified and last-accessed timestamps for all text files
  in the current directory to the current point in time.

  .EXAMPLE
  Touch-File newfile1, newfile2 -PassThru

  Creates files 'newfile1' and 'newfile2' and outputs information about them as 
  System.IO.FileInfo instances.
  Note the need to use "," to pass multiple paths.

  .EXAMPLE
  Touch-File *.txt -DateTime (Get-Date).Date

  Updates the last-modified and last-accessed timestamps for all text files
  in the current directory to midnight (the start of) of today's date.

  .EXAMPLE
  Get-Item *.txt | Touch-File -Offset '-1:0'

  Adjusts the last-modified and last-accessed timestamps of all text files
  in the current directory back by 1 hour.

  .EXAMPLE
  Get-ChildItem -File | Touch-File -ReferencePath .

  Sets the last-modified and last-accessed timestamps of all files in the 
  current directory to the last-modified timestamp of the current directory.


  .NOTES
  "Touch" is not an approved verb in PowerShell, but it was chosen nonetheless,
  because none of the approved verbs can adequately convey the core functionality
  of this command.

  In PowerShell *Core*, implementing this command to support multiple target
  paths *as individual arguments* (as in Unix touch) would be possible
  (via ValueFromRemainingArguments), but such a solution would misbehave in
  Windows PowerShell.

  #>

  # Supports both editions, but requires PSv3+
  #requires -version 3  

  [CmdletBinding(DefaultParameterSetName = 'Path', SupportsShouldProcess)]
  param(
    [Parameter(ParameterSetName = 'Path', Mandatory, Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName)]
    [Parameter(ParameterSetName = 'PathAndDateTime', Mandatory, Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName)]
    [Parameter(ParameterSetName = 'PathAndRefPath', Mandatory, Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName)]
    [string[]] $Path
    ,
    [Parameter(ParameterSetName = 'LiteralPath', Mandatory, ValueFromPipelineByPropertyName)]
    [Parameter(ParameterSetName = 'LiteralPathAndDateTime', Mandatory, ValueFromPipelineByPropertyName)]
    [Parameter(ParameterSetName = 'LiteralPathAndRefPath', Mandatory, ValueFromPipelineByPropertyName)]
    [Alias('PSPath', 'LP')]
    [string[]] $LiteralPath
    ,
    [Parameter(ParameterSetName = 'PathAndRefPath', Mandatory)]
    [Parameter(ParameterSetName = 'LiteralPathAndRefPath', Mandatory)]
    [string] $ReferencePath
    ,
    [Parameter(ParameterSetName = 'PathAndDateTime', Mandatory)]
    [Parameter(ParameterSetName = 'LiteralPathAndDateTime', Mandatory)]
    [datetime] $DateTime
    ,
    [Parameter(ParameterSetName = 'Path')]
    [Parameter(ParameterSetName = 'LiteralPath')]
    [timespan] $Offset
    ,
    [switch] $NoNew
    ,
    [switch] $PassThru
    ,
    [switch] $Force
  )

  begin { 
    
    Set-StrictMode -Version 1
    $ErrorActionPreference = 'Continue' # We want to pass non-terminating errors / .NET method-call exceptions through.

    $haveRefPath = $PSBoundParameters.ContainsKey('ReferencePath')
    $haveDateTime = $PSBoundParameters.ContainsKey('DateTime')
    $haveOffset = $PSBoundParameters.ContainsKey('Offset')
    if ($haveOffset) { $NoNew = $true } # -NoNew is implied.
    # Initialize defaults (even though they may not be used).
    # Defining them unconditionally prevents strict-mode violations in pseudo-ternary conditionals.
    if (-not ($haveDateTime -or $haveRefPath)) { $DateTime = [datetime]::Now }
    if (-not $haveOffset) { $Offset = 0 }
    # If a reference item was given, obtain its timestamp now and abort if that fails.
    if ($haveRefPath) {
      try {
        $DateTime = (Get-Item -ErrorAction Stop $ReferencePath).LastWriteTime
      }
      catch {
        Throw "Failed to get the reference path's last-modified timestamp: $_"
      }
    }
    $touchedCount = 0

  }

  process {
    
    $wildcardsSupported = $PSCmdlet.ParameterSetName -notlike 'LiteralPath*'

    # Try to retrieve existing files.
    [array] $files, $dirs = 
    $(
      if ($wildcardsSupported) {
        Get-Item -Path $Path -ErrorAction SilentlyContinue -ErrorVariable err -Force:$Force
      }
      else {
        Get-Item -LiteralPath $LiteralPath -ErrorAction SilentlyContinue -ErrorVariable err -Force:$Force
      }
    ).Where( { -not $_.PSIsContainer }, 'Split')

    # Ignore directories among the (globbed) input paths, but issue a warning.
    if ($dirs) {
      Write-Warning "Ignoring *directory* path(s): $dirs"
    }

    # -WhatIf / -Confirm support.
    # Note: The prompt message is also printed with -Verbose
    $targets = ($LiteralPath, ($Path, $files.FullName)[$files.Count -gt 0])[$wildcardsSupported] -replace '^Microsoft\.PowerShell\.Core\\FileSystem::' -replace ('^' + [regex]::Escape($PWD) + '[\\/]?')
    if ($targets.Count -gt 1) { $targets = "`n" + ($targets -join "`n") + "`n" }
    $newDateTimeDescr = if ($haveOffset -and -not ($haveDateTime -or $haveRefPath)) { "the last-modified timestamp offset by $Offset" } else { "$($DateTime + $Offset)" }
    $actionDescr = ("Updating / creating with a last-modified timestamp of $newDateTimeDescr", "Updating the last-modified timestamp to $newDateTimeDescr")[$NoNew.IsPresent]
    if (-not $PSCmdlet.ShouldProcess($targets, $actionDescr)) { return }

    # Try to create the files that don't yet exist - unless opt-out -NoNew was specified.
    if ($err) {
      if ($NoNew) {
        Write-Warning "Ignoring non-existing path(s): $($err.TargetObject)"
      }
      else {
        $files += foreach ($file in $err.TargetObject) {
          Write-Verbose "Creating: $file..."
          New-Item -ItemType File -Path $file -ErrorAction SilentlyContinue -ErrorVariable e
          # If an error occurred - such as the parent directory of a literal target path not existing - pass it through.
          # The only acceptable error is if the file has just been created, between now and the time we ran Get-Item above.
          if ($e -and -not (Test-Path -PathType Leaf -LiteralPath $file)) { $e | Write-Error }
        }
      }
    }

    # Update the target files' timestamps.
    foreach ($file in $files) {
      # Note: If $file is a symlink, *setting* timestamp properties invariably sets the *target*'s timestamps.
      #       *Getting* a symlink's timestame properties, by contrast, reports the *link*'s.
      #       This means:
      #          * In order to apply an offset to the existing timestamp, we must explicitly get the *target*'s timestamp
      #          * With -PassThru, unfortunately - given that we don't want to quietly switch to the *target* on output -
      #            this means that the passed-through instance will reflect the - unmodified - *link*'s properties.
      $target = 
        if ($haveOffset -and $file.LinkType) {
          # Note: If a link's target doesn't exist, a non-terminating error occurs, which we'll pass through.
          # !! Due to inconsistent behavior of Get-Item as of PowerShell Core 7.2.0-preview.5, if a broken symlink
          # !! is (a) specified literally and (b) alongside at least one other path (irrespective of whether -Path or -LiteralPath is used),
          # !! it generates an *error* - even though passing that path *as the only one* or *by indirect inclusion via a pattern*
          # !! does NOT (it lists the non-existent target in the Name column, but doesn't error).
          # !! Thus, if (a) and (b) apply, the resulting error may have caused the non-existent target to be created above,
          # !! assuming that its parent directory exists.
          Get-Item -Force -LiteralPath $file.Target
        } else { 
          $file 
        }
      if ($target) {
        # Set the last-modified and (always also) the last-access timestamps.
        $target.LastWriteTime = $target.LastAccessTime = if ($haveOffset) { $target.LastWriteTime + $Offset } else { $DateTime }
      }
      if ($PassThru) { $file }
    }
    $touchedCount += $files.Count

  }

  end {  
    if (-not $WhatIfPreference -and $touchedCount -eq 0) {
      Write-Warning "Nothing to touch."
    }
  }

}

[1] See this answer for how to do that.

Upvotes: 4

Mark
Mark

Reputation: 434

You can use

$file = Get-Item C:\Intel\Logs\IntelCpHDCPSvc.log
$file.LastWriteTime = (Get-Date)

Or CreationTime or LastAcccessTime, if you prefer.

As a function

Function UpdateFileLastWriteTimeToToday() { 
        Param ($FileName)
    $file = Get-Item $FileName
    Echo "Current last write time: " $file.LastWriteTime
    $file.LastWriteTime = (Get-Date)
    Echo "New last write time: " $file.LastWriteTime
}

#How to use

UpdateFileLastWriteTimeToToday -FileName C:\Intel\Logs\IntelGFX.Log

With the output of

Current last write time: 
Tuesday, April 16, 2019 1:09:49 PM

New last write time: 
Monday, November 4, 2019 9:59:55 AM

Upvotes: 5

Related Questions