Skip to content

Instantly share code, notes, and snippets.

@gravejester
Last active Aug 29, 2015
Embed
What would you like to do?
function Out-File2{
<#
.SYNOPSIS
Sends output to a file.
.DESCRIPTION
This function extends the original Out-File cmdlet by also handling automatic backup
of files. You can choose to use either a running counter, or the current date as a file
suffix for the backed up files.
.EXAMPLE
'Some text' | Out-File2 -FilePath $path
Used liked this, the function works just as the regular Out-File.
.EXAMPLE
'Some text' | Out-File2 -FilePath $path -Append -FilesToKeep 5 -SuffixType Counter -CounterLength 4 -MaxFileSize 1mb
In this example we are appending to the file, and are using 5 backup files with a file suffix type of counter. The counter is 4 digits long,
and a new backup will be made when the original file exeeds 1MB in size.
.EXAMPLE
'Some text' | Out-File2 -FilePath $path -Append -FilesToKeep 5 -SuffixType Date -MaxFileSize 1mb
Same example as above, but with a suffix type of date instead.
.NOTES
NOTE that when using a custom date format for the date suffix, it's possible to get backup files that don't have unique file names.
This will result in an error. Also, if you change suffix type while using the same file name, the function will get confused, and
behave unexpectedly.
Author: Øyvind Kallstad
Date: 29.04.2015
Version: 1.0
#>
[CmdletBinding()]
param (
# Specifies the objects to be written to the file. Enter a variable that contains the objects or type a command or
# expression that gets the objects.
[Parameter(ValueFromPipeline)]
[PSObject] $InputObject,
# Specifies the path to the output file.
[Parameter(Position = 0, Mandatory)]
[ValidateNotNullOrEmpty()]
[string] $FilePath,
# Specifies the type of character encoding used in the file. Valid values are "Unicode", "UTF7", "UTF8", "UTF32",
# "ASCII", "BigEndianUnicode", "Default" and "OEM". "Unicode" is the default.
[Parameter(Position = 1)]
[ValidateSet('Unicode','UTF7','UTF8','UTF32','ASCII','BigEndianUnicode','Default','OEM')]
[string] $Encoding = 'Unicode',
# Adds the output to the end of an existing file, instead of replacing the file contents.
[Parameter()]
[switch] $Append,
# Allows the function to overwrite an existing read-only file. Even using the Force parameter, the function cannot
# override security restrictions.
[Parameter()]
[switch] $Force,
# Will not overwrite (replace the contents) of an existing file. By default, if a file exists in the specified path,
# Out-File2 overwrites the file without warning. If both Append and NoClobber are used, the output is appended
# to the existing file.
[Parameter()]
[switch] $NoClobber,
# Specifies the maximum allowed file size (in bytes) for the file being written to.
# A MaxFileSize of 0 means no size limit. Default is 0.
[Parameter()]
[ValidateRange(1,[int64]::MaxValue)]
[int64] $MaxFileSize = 0,
# Specifies how many files you want to keep, before the function deletes old versions of the file. Default is 1.
# If MaxFileSize is 0 or Append is False, this parameter have not effect.
[Parameter()]
[ValidateRange(1,[int32]::MaxValue)]
[int] $FilesToKeep = 3,
# Choose what kind of file suffix you want to use for the backed up old versions of the file. Valid values are
# "Counter" and "Date". Default is "Counter".
# If MaxFileSize is 0 or Append is False, this parameter have not effect.
[Parameter()]
[ValidateSet('Counter','Date')]
[string] $SuffixType = 'Counter',
# Specify the character(s) to use as separator between the filename and the suffix. Default is "_".
# If MaxFileSize is 0 or Append is False, this parameter have not effect.
[Parameter()]
[ValidateNotNullOrEmpty()]
[string] $SuffixSeparator = '_',
# If suffix type is Counter, this parameter lets you specify the length of the counter, meaning the number of
# digits used in the counter suffix. Default is 3.
# If MaxFileSize is 0 or Append is False, this parameter have not effect.
[Parameter()]
[int] $CounterLength = 3,
# Specify the date format used for the date suffix type. Default is 'yyyyMMddHHmmssff'
# If MaxFileSize is 0 or Append is False, this parameter have not effect.
[Parameter()]
[ValidateNotNullOrEmpty()]
[string] $DateFormat = 'yyyyMMddHHmmssff'
)
try {
# get a list of all backup files based on the suffix type, and remove the oldest ones if the amount is larger than
# the FilesToKeep value. If any files are removed, get a new list so it's correct, as we will be using it further down in the script.
if ($SuffixType -eq 'Counter') {
[System.Array]$backupFiles = Get-Item -Path "$(Join-Path -Path $file.DirectoryName -ChildPath $file.BaseName)$($SuffixSeparator)?*$($file.Extension)" | Where-Object {!$_.PSIsContainer} | Sort-Object LastWriteTime -Descending | Sort-Object Name
if ($backupFiles.Count -gt $FilesToKeep) {
$backupFiles | Sort-Object Name | Select-Object -Last ($backupFiles.Count - $FilesToKeep) | Remove-Item -Force:$Force -ErrorAction Stop
[System.Array]$backupFiles = Get-Item -Path "$(Join-Path -Path $file.DirectoryName -ChildPath $file.BaseName)$($SuffixSeparator)?*$($file.Extension)" | Where-Object {!$_.PSIsContainer} | Sort-Object LastWriteTime -Descending | Sort-Object Name
}
}
else {
[System.Array]$backupFiles = Get-Item -Path "$(Join-Path -Path $file.DirectoryName -ChildPath $file.BaseName)$($SuffixSeparator)?*$($file.Extension)" | Where-Object {!$_.PSIsContainer} | Sort-Object LastWriteTime
if ($backupFiles.Count -gt $FilesToKeep) {
$backupFiles | Sort-Object LastWriteTime -Descending | Select-Object -Last ($backupFiles.Count - $FilesToKeep) | Remove-Item -Force:$Force -ErrorAction Stop
[System.Array]$backupFiles = Get-Item -Path "$(Join-Path -Path $file.DirectoryName -ChildPath $file.BaseName)$($SuffixSeparator)?*$($file.Extension)" | Where-Object {!$_.PSIsContainer} | Sort-Object LastWriteTime
}
}
if (((Test-Path -Path $FilePath) -eq $true) -and ($MaxFileSize -gt 0) -and ($Append)) {
$file = Get-Item -Path $FilePath
if ($file.Length -ge $MaxFileSize) {
if ($backupFiles.Count -eq $FilesToKeep) {
# remove the oldest of the backup files
$fileToDelete = $backupFiles[0]
Write-Verbose "Removing $($fileToDelete.Name)"
$fileToDelete | Remove-Item -Force:$Force -ErrorAction Stop
# for files with a suffix type of counter, we need to rename the remaining files so the counter makes sense.
if ($SuffixType -eq 'Counter') {
foreach ($backupFile in $backupFiles[1..($backupFiles.IndexOf($backupFiles[-1]))]) {
$thisNewFileName = "$($backupFile.BaseName.Split($SuffixSeparator)[0])$($SuffixSeparator)$([Convert]::ToString([Convert]::ToInt32($backupFile.BaseName.Split($SuffixSeparator)[-1])-1).PadLeft($CounterLength,'0'))$($backupFile.Extension)"
Write-Verbose "Renaming $($backupFile.Name) to $thisNewFileName"
Rename-Item -Path $backupFile -NewName $thisNewFileName -Force -ErrorAction Stop
}
}
# rename the current file while adding the correct suffix
if ($SuffixType -eq 'Counter') {
$thisNewFileName = "$($file.BaseName.Split($SuffixSeparator)[0])$($SuffixSeparator)$([Convert]::ToString([Convert]::ToInt32($backupFiles[-1].BaseName.Split($SuffixSeparator)[-1])).PadLeft($CounterLength,'0'))$($file.Extension)"
}
else {
$thisNewFileName = "$($file.BaseName.Split($SuffixSeparator)[0])$($SuffixSeparator)$(Get-Date -Format $DateFormat)$($file.Extension)"
}
Write-Verbose "Renaming $($file.Name) to $thisNewFileName"
Rename-Item -Path $file -NewName $thisNewFileName -Force -ErrorAction Stop
}
# if the FilesToKeep limit still haven't been reached
else {
# again we have to treat files using the Counter suffix type differently
# if this is the first time a backup is made, use 1 as the counter suffix
# if other backup files exist, take the last counter value found, and add 1
if ($SuffixType -eq 'Counter') {
if ($backupFiles.Count -ge 1) {
$thisNewFileName = "$($file.BaseName)$($SuffixSeparator)$([Convert]::ToString([Convert]::ToInt32($backupFiles[-1].BaseName.Split($SuffixSeparator)[-1])+1).PadLeft($CounterLength,'0'))$($file.Extension)"
}
else {
$thisNewFileName = "$($file.BaseName)$($SuffixSeparator)$([Convert]::ToString(1).PadLeft($CounterLength,'0'))$($file.Extension)"
}
Write-Verbose "Renaming $($file.Name) to $($thisNewFileName)"
Rename-Item $file -NewName $thisNewFileName -Force -ErrorAction Stop
}
else {
$thisNewFileName = "$($file.BaseName)$($SuffixSeparator)$(Get-Date -Format $DateFormat)$($file.Extension)"
Write-Verbose "Renaming $($file.Name) to $($thisNewFileName)"
Rename-Item $file -NewName $thisNewFileName -Force -ErrorAction Stop
}
}
}
}
Write-Verbose "Writing $FilePath"
Out-File -FilePath $FilePath -InputObject $InputObject -Encoding $Encoding -Append:$Append -Force:$Force -NoClobber:$NoClobber -ErrorAction Stop
}
catch [System.IO.IOException] {
Write-Warning $_.Exception.Message
if ($SuffixType -eq 'Date') {
Write-Warning 'Make sure your date format creates unique backup files.'
}
}
catch {
Write-Warning $_.Exception.Message
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment