The 2013 Powershell Scripting Games have started for real. I am competing this is for the firs time in the Advanced category. For the moment two tasks have been published. In the first one we had to develop a function aimed at moving old logfiles to a location off the system drive. Easy right? Well, not really. The judges (as well as the other competitors) are very nitty-picky and I have soon discovered that any small mistake can bring very low scores.
Even tough I paid careful attention to what I was doing, I overlooked a few things which I want to share here.
Here's the function I wrote for the assignment:
- #Requires -Version 3.0
- set strict-mode latest
- function Archive-Logs {
- <#
- .SYNOPSIS
- Archives application logs older than a certain amount of days to a specific location.
- .DESCRIPTION
- Moves all the application logs older than a certain amount of days to a specific location.
- The subfolder structure is maintained.
- .PARAMETER SourcePath
- Specifies the current path of the folder containing the logs to be moved.
- .PARAMETER DestinationPath
- Specifies the destination path where to recreate the subfolders structure and move the logs.
- .PARAMETER Days
- Specifies the minimum age in days of the logs that have to be archived.
- The default is 90 days.
- .PARAMETER Log
- Switch parameter to enable error logging.
- The default is False.
- .PARAMETER FailedLogFile
- Specifies the path of the file where the errors are logged if the Log switch is set to True.
- The default is $env:temp\failed-log-move.log
- .PARAMETER ViewErrorLog
- Opens the error log for viewing using the default associated program (i.e. Notepad).
- The default is False.
- .EXAMPLE
- Archives logs older than 100 days from C:\Application\Log to \\nasserver\Archives
- Archive-Logs C:\Application\Log \\nasserver\Archives 100
- .EXAMPLE
- Archives logs older than 100 days from C:\Application\Log to \\nasserver\Archives. It also logs errors to $env:temp\failed-log-move.log and opens this log for reading.
- Archive-Logs C:\Application\Log \\nasserver\Archives 100 -Log -ViewErrorLog
- .EXAMPLE
- Archives logs older than 2 years from C:\Application\Log to \\nasserver\Archives and shows verbose output
- Archive-Logs C:\Application\Log \\nasserver\Archives 730 -Verbose
- .EXAMPLE
- Simulates archival of logs older than 3 months from C:\Application\Log to d:\backup
- Archive-Logs C:\Application\Log d:\backup 90 -Whatif
- .NOTES
- Entry for 2013SG Advanced Event 1. To use, dot source script and run 'Archive-Logs'.
- Use 'get-help Archive-Logs -full' to see how to run complete help.
- This function leverages Powershell V3 cmdlets and parameters, such as OlderThan in Test-Path or $PSItem instead of $_.
- I am also taking advantage of Get-ChildItem -File and -Directory.
- #>
- [CmdletBinding(SupportsShouldProcess=$True)]
- param(
- [Parameter(Mandatory, Position=0)]
- [ValidateScript({Test-Path $_ -PathType Container})]
- [String]$SourcePath,
- [Parameter(Mandatory, Position=1)]
- [ValidateScript({Test-Path $_ -PathType Container})]
- [String]$DestinationPath,
- [Parameter(Mandatory=$False, Position=2)]
- [Int]$Days = 90,
- [Parameter(Mandatory=$False, Position=3, ParameterSetName = 'Logging')]
- [Switch]$Log = $False,
- [Parameter(Mandatory=$False, Position=4, ParameterSetName = 'Logging')]
- [ValidateScript(
- {
- Try {
- Add-Content -LiteralPath $_ -Value $null -ErrorAction Stop
- $true
- }
- Catch { $false }
- })]
- [String]$FailedLogFile=(Join-Path $env:Temp 'failed-log-move.log'),
- [Parameter(Mandatory=$false, Position=5, ParameterSetName = 'Logging')]
- [switch]$ViewErrorLog = $false
- )
- Write-Verbose ("Script started processing at {0:hh}:{0:mm}:{0:ss}" -f (Get-Date))
- $ErrorTracker = 'without errors'
- ForEach($SubFolder in (Get-ChildItem $SourcePath -Directory))
- {
- $DestSubFolder = Join-Path -Path $DestinationPath -ChildPath $SubFolder
- if(!(Test-Path $DestSubFolder))
- {
- Try {
- [void] (New-Item -path $DestinationPath -name $SubFolder -ItemType directory -Force -ErrorAction Stop)
- }
- Catch
- {
- Write-Warning "Failed creating $SubFolder in $DestinationPath"
- $ErrorTracker = 'with errors'
- if ($Log)
- {
- Add-Content -LiteralPath $FailedLogFile -Value "$(Get-Date) - Failed creating $SubFolder in $DestinationPath"
- }
- } # end Try/Catch
- }
- Try {
- $OldLogs = Get-ChildItem -Path $SubFolder.FullName -Recurse -File -Include *.log -ErrorAction Stop |
- Where-Object { Test-Path -Path $PSItem.FullName -OlderThan (Get-Date).AddDays(-$Days) }
- }
- Catch
- {
- Write-Warning "An error occurred retrieving logs from $($SubFolder.fullname)"
- $ErrorTracker = 'with errors'
- if ($Log)
- {
- Add-Content -LiteralPath $FailedLogFile -Value "$(Get-Date) - An error occurred retrieving logs from $($SubFolder.fullname)"
- }
- } # end Try/Catch
- if($OldLogs)
- {
- $OldLogs | Foreach-Object {
- Try {
- $CurrentLog = $PSItem
- Write-Verbose "Archiving $($CurrentLog.name) [$($CurrentLog.lastWriteTime)]"
- $CurrentLog | Move-Item -Destination $DestSubFolder -Force -ErrorAction Stop
- }
- Catch {
- Write-Warning "An error occurred moving $($CurrentLog.fullname)"
- $ErrorTracker = 'with errors'
- if ($Log)
- {
- Add-Content -LiteralPath $FailedLogFile -Value "$(Get-Date) - An error occurred moving $($CurrentLog.fullname)"
- }
- } # end Try/Catch
- }
- }
- } # end ForEach-Object
- Write-Verbose ("Script finished processing $ErrorTracker at {0:hh}:{0:mm}:{0:ss}" -f (Get-Date))
- if($ViewErrorLog)
- {
- & $FailedLogFile
- }
- } # end Function Archive-Logs
Apparently everything is well. Unfortunately there are a few mistakes that make me score 2.8 out of 5 possible points.
Let's start with the biggest mistake, which is:
Let's start with the biggest mistake, which is:
- Get-ChildItem -Path $SubFolder.FullName -Recurse -File -Include *.log -ErrorAction Stop
The solution to this, I give credit to Mike for this, was to include the desired file extension into the Path parameter:
- Get-ChildItem -Path (Join-Path -Path $SourcePath -ChildPath '*\*.log') -ErrorAction Stop
Another big error of mine was not to comment enough. Many people stated that my script was hard to follow. The fact is that I deemed unnecessary to explain how my code worked to people who already have a proficient knowledge of Powershell. I was wrong.
The lesson I get from this is always to put comments before complicated lines of code as well as the end of each scriptblock. Here's two examples:
- foreach ($Computer in $ComputerName) {
- ... any action ...
- } # end of foreach $Computer
- if($pingworks){
- get-wmiobject...
- } # end of If block
This makes my script much more readable.
The final thing that I learned from Event 1 and that I want to share with you is never to use superflous code. An example could be specifying that a parameters is not mandatory:
- [Parameter(Mandatory=$False]
That's all for the for Event 1. If you want to see it on the ScriptingGames website click on the image. Unfortunately is too late to vote for my entry on this assignment.
In the next post I will talk about the Second Event of the Games. Stay tuned!
No comments:
Post a Comment