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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |