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 |