public/Log-Rotate.ps1
# Log-Rotate Cmdlet function Log-Rotate { <# .SYNOPSIS A replica of the logrotate utility, except this also runs on Windows systems. .DESCRIPTION The functionality of Log-Rotate was ported from the original logrotate. It is made to work in the exact way logrotate would work: Same rotation logic, same outputs, same configurations. Best of all, it works on one more platform: Windows. .PARAMETER Config The path to the Log-Rotate config file, or the path to a directory containing config files. If a directory is given, all files will be read as config files. Any number of config file paths can be given. Later config files will override earlier ones. The best method is to use a single config file that includes other config files by using the 'include' directive. .PARAMETER ConfigAsString The configuration as a string, accepting input from the pipeline. Especially useful when you don't want to use a separate config file. .PARAMETER Debug In debug mode, no logs are rotated. Use this to validate your configs or observe rotation logic. .PARAMETER Force Forces Log-Rotate to perform a rotation for all Logs, even when Log-Rotate deems particular Log(s) to not require rotation. .PARAMETER Help Prints Help information. .PARAMETER Mail Tells logrotate which command to use when mailing logs. .PARAMETER State The path to a Log-Rotate state file to use for previously rotated Logs. May be absolute or relative. If no state file is provided, by default the location of the state file (named 'Log-Rotate.state') will be in the calling script's directory. If there is no calling script, the location of the state file will be in the current working directory. If a relative path is provided, the state file path will be resolved to the current working directory. If a tilde ('~') is used at the beginning of the path, the state file path will be resolved to the user's home directory. .PARAMETER Usage Prints Usage information .EXAMPLE Log-Rotate -ConfigAsString $configAsString -State $state -Verbose .EXAMPLE Log-Rotate -Config "/etc/Log-Rotate.conf" -State "/var/lib/Log-Rotate/Log-Rotate.status" -Verbose .EXAMPLE Log-Rotate -Config "/etc/configs/" -Verbose .LINK https://github.com/theohbrothers/Log-Rotate .NOTES *logrotate manual: https://linux.die.net/man/8/logrotate The command line is identical to the actual logrotate utility, if parameter aliases are used. If using full parameters, only optional (-mail, -state) and miscellaneous (-usage, -help) parameters use one instead of two dashes. (i.e. -mail instead of --mail) For help on command line options, use: Get-Help Log-Rotate -detailed Configuration file(s) should follow the same format and options used by the actual logrotate utility. See the logrotate manual* for configuration options. Because logrotate is constantly being updated, the present utility may not be up to par with it. But it won't be too hard or too long for new features to be integrated. #> [CmdletBinding()] param ( [Parameter(ValueFromPipeline)] [string]$ConfigAsString , [alias("c")] [string[]]$Config , [alias("d")] [switch]$WhatIf , [alias("f")] [switch]$Force , [alias("h")] [switch]$Help , [alias("m")] [string]$Mail , [alias("s")] [string]$State , [alias("u")] [switch]$Usage ) if ($WhatIf) { Write-Warning "We are in Debug mode. No logs will be rotated." $VerbosePreference = 'Continue' } if ($Force) { Write-Warning "We are in Forced-Rotation mode." } # Use Caller Error action if specified $CallerEA = $ErrorActionPreference $ErrorActionPreference = 'Stop' # Always use verbose mode? #$VerbosePreference = 'Continue' # PS Defaults $PSDefaultParameterValues['*-Content:Force'] = $true $PSDefaultParameterValues['*-Item:Force'] = $true $PSDefaultParameterValues['Get-ChildItem:Force'] = $true $PSDefaultParameterValues['Out-File:Force'] = $true $PSDefaultParameterValues['Invoke-Command:ErrorAction'] = 'Stop' # Prints and exits if ($Help) { Write-Output (Get-Help Log-Rotate -Full) return } if ($Usage) { Write-Output (Get-Help Log-Rotate) return } try { Write-Verbose "------------------------------ Log-Rotate --------------------------------------" # Will always reflect the calling script's path, even when used as a Module if ($MyInvocation.PSCommandPath) { Write-Verbose "Script root: $( Split-Path -parent $MyInvocation.PSCommandPath )" } #Write-Verbose "Current working directory: $( Convert-Path . )" Write-Verbose "Current working directory: $( $(Get-Location).Path )" # Get the configuration as a string if ($ConfigAsString) { # Pipelined string. Keep going $MultipleConfig = $ConfigAsString }else { # No pipeline string. From this point on $Config has to be an array of: a path to a config file, or directory containing config files. if (!$Config) { Write-Error "No config file(s) specified." -ErrorAction Stop } try { $MultipleConfig = '' $Config | ForEach-Object { # Path has to be valid if (Test-Path $_) { $item = Get-Item $_ if (Test-Path $item.FullName -PathType Container) { # It's a directory. Consider all child files as config files. Get-ChildItem $item.FullName -File | ForEach-Object { Write-Verbose "Config file found: $($_.FullName)" $MultipleConfig += "`n" + (Get-Content $_.FullName -Raw -ErrorAction Stop) } }else { # It's a file. Write-Verbose "Config file found: $($item.FullName)" $MultipleConfig += "`n" + (Get-Content $item.FullName -Raw -ErrorAction Stop) } }else { throw "Invalid config path specified: $_" } } }catch { Write-Error "Unable to retrieve content of config $Config" -ErrorAction Continue throw } } # Instantiate our BlockFactory and LogFactory #$BlockFactory = $BlockFactory.psobject.copy() #$LogFactory = $LogFactory.psobject.copy() # Compile our Full Config $FullConfig = Compile-Full-Config $MultipleConfig # Validate our Full Config Validate-Full-Config $FullConfig # Instantiate Singletons $BlockFactory = New-BlockFactory $LogFactory = New-LogFactory $LogObject = New-LogObject # Create Blocks from our Full Config $BlockFactory.Create($FullConfig) # Initialize our Rotation Status $LogFactory.InitStatus($State) $count = 0 $BlockFactory.GetAll().GetEnumerator() | ForEach-Object { $count += $_.Value.LogFiles.Count } Write-Verbose "Handling $count logs" # Run Log-Rotate for each defined block $blocks = $BlockFactory.GetAll() $blocks.GetEnumerator() | ForEach-Object { # This block object. $block = $_.Value $blockoptions = $block.Options # Rotate each log of this block Process-Local-Block -block $block @blockoptions } # Finish up with dumping status $LogFactory.DumpStatus() }catch { Write-Error -ErrorRecord $_ -ErrorAction $CallerEA } } |