Touch is a well known command in the Unix/Linux world. It is used to update the time stamp of files, and additionally will create a new empty file if it doesn’t exist already (unless you use the -c parameter, which will suppress this behaviour).
Invoke-Touch is a PowerShell implementation of Touch, and supports all the functionality of the original command. I have added the Unix/Linux parameter names as aliases, so if you are familiar with touch, you shouldn’t have any problems using this function.
There are however some differences, that I will explain in detail. The first one is that the original command have two different parameters to update the time stamp; -d and -t. In this function I have only one parameter for custom time stamps, but I have added both ‘d’ and ‘t’ as alias for it.
I have seen some specifications of touch where there also are two parameters that lets you set an offset in seconds for the time stamp (-B for positive offset, and -F for negative offset). I have included this functionality in the form of a -OffsetSeconds parameters where you can use positive and negative values to get the same functionality.
I have also added a -PassThru parameter that, when used, will pass the updated ‘fileinfo’ object back to the pipeline for further processing.
NOTE! Invoke-Touch uses Resolve-PathEx internally, and will give you a nasty error if it can’t find it!
I’m not going to go into details of how to use Invoke-Touch as there are tons of pages online that explain how to use the original touch command. You can also use Get-Help to see the internal help for the function.
function Invoke-Touch { | |
<# | |
.SYNOPSIS | |
PowerShell inplementation of the Unix/Linux utility called touch. | |
.DESCRIPTION | |
Touch let's you update the access date and / or modification date of a file. If the file don't exist, an empty file will be created | |
unless you use the DoNotCreateNewFile parameter. This implementation have the original parameter names added as | |
aliases, so if you are familiar with the original touch utility it should be easy to use this one. | |
.EXAMPLE | |
Invoke-Touch newfile | |
Will create a new empty file called 'newfile' in the current folder. | |
.EXAMPLE | |
Invoke-Touch newfile3 -DateTime '10.10.2014' | |
Will create a new empty file called 'newfile3' with the provided date. | |
.EXAMPLE | |
Invoke-Touch newfile -r newfile3 | |
Will update the timestamp of 'newfile' using 'newfile3' as a reference. | |
.LINK | |
https://gist.github.com/gravejester/f4934a5ce16c652d11d3 | |
.NOTES | |
*** THIS FUNCTION IS USING 'Resolve-PathEx' AND WON'T WORK WITHOUT IT *** | |
Author: Øyvind Kallstad | |
Date: 13.11.2014 | |
Version: 1.0 | |
#> | |
[CmdletBinding(ConfirmImpact = 'Low',SupportsShouldProcess, DefaultParameterSetName = 'UserDateTime')] | |
param ( | |
# Filename and/or path. | |
[Parameter(Position = 0, ValueFromPipeline, ValueFromPipelinebyPropertyName)] | |
[string[]] $Path, | |
# File to use as a timestamp reference. | |
[Parameter(ParameterSetName = 'ReferenceDateTime')] | |
[Alias('r')] | |
[string] $Reference, | |
# Timestamp offset in seconds. | |
[Parameter()] | |
[Alias('B','F')] | |
[int] $OffsetSeconds = 0, | |
# Used to override the timestamp. If omitted the current date and time will be used. | |
[Parameter(ParameterSetName = 'UserDateTime')] | |
[Alias('t','d')] | |
[string] $DateTime, | |
# Update Last Access Time. | |
[Parameter()] | |
[Alias('a')] | |
[switch] $AccessTime, | |
# Update Last Write Time. | |
[Parameter()] | |
[Alias('m','w')] | |
[switch] $WriteTime, | |
# Switch to override the basic functionality of creating a new file if it don't exist already. | |
[Parameter()] | |
[Alias('c')] | |
[switch] $DoNotCreateNewFile, | |
[Parameter()] | |
[switch] $PassThru | |
) | |
BEGIN { | |
try { | |
# use timestamp from a reference file | |
if (-not([string]::IsNullOrEmpty($Reference))) { | |
if (Test-Path $Reference) { | |
$referenceFile = Get-ChildItem –Path $Reference | |
$newLastAccessTime = ($referenceFile.LastAccessTime).AddSeconds($OffsetSeconds) | |
$newLastWriteTime = ($referenceFile.LastWriteTime).AddSeconds($OffsetSeconds) | |
Write-Verbose "Using timestamp from $Reference" | |
} | |
else { | |
Write-Warning "$Reference not found!" | |
} | |
} | |
# use timestamp from user input | |
elseif (-not([string]::IsNullOrEmpty($DateTime))) { | |
$userDateTime = [DateTime]::Parse($DateTime,[CultureInfo]::CurrentCulture,[System.Globalization.DateTimeStyles]::NoCurrentDateDefault) | |
Write-Verbose "Using timestamp from user input: $DateTime (Parsed: $($userDateTime))" | |
} | |
# use timestamp from current date/time | |
else { | |
$currentDateTime = (Get-Date).AddSeconds($OffsetSeconds) | |
$newLastAccessTime = $currentDateTime | |
$newLastWriteTime = $currentDateTime | |
Write-Verbose "Using timestamp from current date/time: $currentDateTime" | |
} | |
} | |
catch { | |
Write-Warning $_.Exception.Message | |
} | |
} | |
PROCESS { | |
foreach ($thisPath in $Path) { | |
try { | |
$thisPathResolved = Resolve-PathEx –Path $thisPath | |
foreach ($p in $thisPathResolved.Path) { | |
Write-Verbose "Resolved path: $p" | |
# if file is not found, and it's ok to create a new file, create it! | |
if (-not(Test-Path $p)) { | |
if ($DoNotCreateNewFile) { | |
Write-Verbose "$p not created" | |
return | |
} | |
else { | |
if ($PSCmdlet.ShouldProcess($p, 'Create File')) { | |
$null = New-Item –path $p –ItemType 'File' –ErrorAction 'Stop' | |
} | |
} | |
} | |
# get fileinfo object | |
$fileObject = Get-ChildItem $p –ErrorAction SilentlyContinue | |
if (-not([string]::IsNullOrEmpty($fileObject))) { | |
# handle date & time if datetime parameter is used | |
if (-not([string]::IsNullOrEmpty($DateTime))) { | |
# if parsed datetime object contains time | |
if ([bool]$userDateTime.TimeOfDay.Ticks) { | |
Write-Verbose 'Found time in datetime' | |
$userTime = $userDateTime.ToLongTimeString() | |
} | |
# else, get time from file | |
else { | |
Write-Verbose 'Did not find time in datetime – using time from file' | |
$userTime = $fileObject.LastAccessTime.ToLongTimeString() | |
} | |
# if parsed datetime object contains date | |
if ([bool]$userDateTime.Date.Ticks) { | |
Write-Verbose 'Found date in datetime' | |
$userDate = $userDateTime.ToShortDateString() | |
} | |
# else, get date from file | |
else { | |
Write-Verbose 'Did not find date in datetime – using date from file' | |
$userDate = $fileObject.LastAccessTime.ToShortDateString() | |
} | |
# parse the new datetime | |
$parsedNewDateTime = [datetime]::Parse("$userDate $userTime") | |
# add offset and save to the appropriate variables | |
$newLastAccessTime = $parsedNewDateTime.AddSeconds($OffsetSeconds) | |
$newLastWriteTime = $parsedNewDateTime.AddSeconds($OffsetSeconds) | |
} | |
} | |
if ($PSCmdlet.ShouldProcess($p, 'Update Timestamp')) { | |
# if neither -AccessTime nor -WriteTime is used, update both Last Access Time and Last Write Time | |
if ((-not($AccessTime)) -and (-not($WriteTime))) { | |
$fileObject.LastAccessTime = $newLastAccessTime | |
$fileObject.LastWriteTime = $newLastWriteTime | |
} | |
else { | |
if ($AccessTime) { $fileObject.LastAccessTime = $newLastAccessTime } | |
if ($WriteTime) { $fileObject.LastWriteTime = $newLastWriteTime } | |
} | |
} | |
} | |
if ($PassThru) { | |
Write-Output (Get-ChildItem $p) | |
} | |
} | |
catch { | |
Write-Warning $_.Exception.Message | |
} | |
} | |
} | |
} | |
New-Alias –Name 'Touch' –Value 'Invoke-Touch' –Force |