# encoding: ascii
# api: powershell
# title: Write-Log
# description: Added support for header to pass into the log, fixed FileCountLimit error proxy to out-file when non-default specified (remove from $PSBoundParameters), set output of $PSBoundParameters add/removal to $null to prevent output of boolean result.
# version: 0.1
# type: function
# author: Jim Ficarra
# license: CC0
# function: Write-Log
# x-poshcode-id: 5554
# x-archived: 2014-11-07T17:16:57
# x-published: 2014-10-30T22:51:00
#
# Note that usage of the parameter aliases will be problematic until fixed with proper removal from $PSBoundParameters.
#
function Write-Log {
  #.Synopsis
  #  Write to a rotating log file
  #.Example
  #  Get-ChildItem C:\ -Recurse | Select FullName, CreationTimeUtc, LastWriteTimeUtc | Write-Log Files.txt -Rotate 5mb
  #
  #  Writes a file list to disc, breaking it into multiple files of approximately 5MB of data each
  [CmdletBinding()]
  param(
    # The file path to log to
    [Parameter(ValueFromPipelineByPropertyName=$true, Mandatory=$true, Position=0)]
    [Alias("PSPath")]
    $FilePath,

    [Parameter(Position=1)]
    [ValidateSet('unknown','string','unicode','bigendianunicode','utf8','utf7','utf32','ascii','default','oem')]
    [ValidateNotNullOrEmpty()]
    [string]
    ${Encoding} = 'utf8',

    [ValidateRange(2, 2147483647)]
    [int]
    ${Width},

    # A max file size at which to rotate the log file
    [Parameter()]
    [Alias("Rotate","Length")]
    [int]$SizeLimit = 10MB,

    # A max file size at which to rotate the log file
    [Parameter()]
    [Alias("Count")]
    [int]$FileCountLimit = 6,
	
	[Parameter(Mandatory=$false)]
	[string]$Header,

    # The data to log
    [Parameter(ValueFromPipeline=$true, Mandatory=$true)]
    $InputObject
  )
  begin { 
	
    try {
        if($PSBoundParameters.ContainsKey('OutBuffer')) {
          $null = $PSBoundParameters.Remove('OutBuffer')
        }
        if($PSBoundParameters.ContainsKey('SizeLimit')) {
          $null = $PSBoundParameters.Remove('SizeLimit')
        }
        if(!$PSBoundParameters.ContainsKey('Encoding')) {
          $null = $PSBoundParameters.Add('Encoding', $Encoding)
        }
		
		if ($PSBoundParameters.ContainsKey('Header')) {
			$null = $PSBoundParameters.Remove('Header')
		}
		
		if ($PSBoundParameters.ContainsKey('FileCountLimit')) {
			$null = $PSBoundParameters.Remove('FileCountLimit')
		}
	
        # Pipeline-bound parameters will be set on $PSBoundParameters later (in the process block)
        # Since we have to (re)create the steppablePipeline, we need a copy of them as they are now        		
		$Parameters = @{} + $PSBoundParameters		
        $wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand(
                          'Microsoft.PowerShell.Utility\Out-File',
                          [System.Management.Automation.CommandTypes]::Cmdlet)
        $scriptCmd = {& $wrappedCmd @Parameters -Append }
        $steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin)
        $steppablePipeline.Begin($PSCmdlet)				
    } catch {
        throw
    }
  }
  process {	
    try {
	  
	  #If header enabled, write it out after out-file creates the empty file on very first run.
	  if (((Get-Item $FilePath).Length -eq 0) -and $Header)
		{			
			$steppablePipeline.Process($Header)
		}
		
      $steppablePipeline.Process($_)
	
      # If the file triggers rotation:
      if(($file = Get-Item $FilePath) -and $file.Length -gt $SizeLimit) {
        # Remove the last item if it would go over the limit
        $steppablePipeline.End()
		
		        
		if(Test-Path "$FilePath.$FileCountLimit") {          
		  Write-Verbose "Removing $FilePath.$FileCountLimit"
          Remove-Item "$FilePath.$FileCountLimit"
        }
        foreach($i in ($FileCountLimit)..1) {			
          if(test-path "$FilePath.$($i-1)") {
            Move-Item "$FilePath.$($i-1)" "$FilePath.$i"
          }
        }
        Move-Item $FilePath "$FilePath.$i"
        $null = New-Item $FilePath -Type File
		
		# Inject header into new file if passed in, otherwise ignore it.
		if ($Header) {			
			$steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin)
			$steppablePipeline.Begin($PSCmdlet)
			$steppablePipeline.Process($Header)
			$steppablePipeline.End()
		}

        $steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin)
        $steppablePipeline.Begin($PSCmdlet)
      }
    } catch {
        throw
    }    
  }
  end
  {
    try {
      $steppablePipeline.End()
    } catch {
      throw
    }
  }
}


## Bonus: the log rotation logic as a stand alone function...
function Rotate-Log {
  #.Synopsis
  #   Rotate a log file if it's gotten too big
  [CmdletBinding()]
  param(
    # The file of the path to test and rotate
    [Parameter(ValueFromPipelineByPropertyName=$true, Mandatory=$true, Position=0)]
    [Alias("PSPath")]
    $FilePath,

    # When a log file goes over this size, rotate it
    $SizeLimit = 10MB,

    # How many backup logs (besides the primary log file) to keep
    $LogLimit  = 5
  )
  process {
    foreach($LogFile in Resolve-Path $FilePath) {
      # If the file triggers rotation:
      if(($file = Get-Item $LogFile) -and $file.Length -gt $SizeLimit) {
        # Remove the last item if it would go over the limit
        if(Test-Path "$LogFile.$LogLimit") { Remove-Item "$LogFile.$LogLimit" }
        foreach($i in $LogLimit..1) {
          if(test-path "$LogFile.$($i-1)") {
            Move-Item "$LogFile.$($i-1)" "$LogFile.$i"
          }
        }
        Move-Item $LogFile "$LogFile.$i"
        $null = New-Item $LogFile -Type File
      }
    }
  }
}
