Tuesday, January 29, 2013

Deleting logs with PowerShell and Scheduled Tasks making the mundane a little less painful

Delete all the things.

This is a new take on an age old problem purging or rotating outdated logging information from servers and workstations alike. I am sure there are hundreds if not thousands of batch files and PowerShell scripts that delete old log files, but what if someone wrote one that not only deletes the old files but is also self aware. Ok.. Maybe not self aware and a far cry from Skynet, but how about one that allows you to schedule and un-schedule it self using the provided parameters as a Windows Scheduled Task.

Well look no further someone has; drop the snippet below into a file called Delete-Logs.ps1 and you too can delete all the things with ease. I have tested this script with Windows 7 and Server 2008 using PowerShell 2.0. While researching I discovered there are also some convenience cmdlets for task scheduler allowing you to manage tasks however, for compatibility reasons I decided stick with schtasks.exe. You will notice I had to remove the path characters from the task name :.(. I had to remove them because of a bug limiting special characters from the scheduled task name.

# Parameters
# -Path A valid absolute path to a log folder or collection of folders script will recursively scan folders
# -Include A wildcard pattern used to include files for consideration 
# -Days Number of days before deleting file from folder
# -Schedule Creates a scheduled task using the provided path, include, and days parameter. Task will run as system user and execute every night at 12:00 AM every 
#  number of days provided in days parameter
# -Unschedule Removes scheduled task that was previously configured for the provided path
# Examples
# Run as stand alone
# .\Delete-Logs.ps1 -Path:'D:\Logs' -Days:4
# Schedule Windows Task
# .\Delete-Logs.ps1 -Path:'C:\Temp' -Include:'*.tmp' -Days:7 -Schedule
# Unschedule Windows Task
# .\Delete-Logs.ps1 -Path:'C:\Temp' -Include:'*.tmp' -Days:7 -Unschedule
Param ($Path='C:\Logs',[string]$Include='*.log', [int]$Days=30, [switch]$Schedule,[switch]$Unschedule)
$Path = New-Object PSObject -Property:@{ Value = $Path; Name = $($Path -replace '\:\\|\\',' ')}
if($Schedule)
{
 Invoke-Expression "C:\Windows\System32\schtasks.exe /create /tr `"`"$PSHome\powershell.exe`" -File '`"'$($MyInvocation.MyCommand.Path)'`"' -Path '`"'$($Path.Value)'`"' -Include '`"'$Include'`"' -Days $Days`" /tn `"Rotate $($Path.Name) every $Days days`" /sc daily /mo $Days /st 00:00 /ru SYSTEM"
}
elseif($Unschedule)
{
 Invoke-Expression "C:\Windows\System32\schtasks.exe /delete /tn `"Rotate $($Path.Name) every $Days days`" /F" 
}
else
{
 $count = 0;
 $start = Get-Date;
 Write-Host "Begin log rotation of $($Path.Value) at $start"
 Get-ChildItem $($Path.Value) -Recurse -Include:$Include | Where-Object {($_.CreationTime -le $(Get-Date).AddDays(-$Days))} | ForEach-Object {
  $date = Get-Date;
  $age = $date.ticks - $_.CreationTime.ticks;
  $age = New-Object -TypeName:'TimeSpan' -ArgumentList:$age;
  Write-Host "Rotating $_ at $date file was $age old";
  Remove-Item $_ -Force;
  $count++;
 }
 $end = Get-Date;
 $duration = $end - $start
 Write-Host "End log rotation of $($Path.Value) at $end $count file(s) were rotated in $duration"
}

Tuesday, January 1, 2013

Batch Converting WMA or WAV to MP3 using PowerShell

A family member of mine is a music collector and has had some trouble in the past managing his library. Initially when it was setup he had been using Windows Media Center to rip media in to his collection. Windows Media Center uses Windows Media Player under the hood and is set by default to encode using WMA format. We didn't notice this until several months later. Windows Media Player makes it easy to switch the encoding format but not so easy to re-encode the existing library.

I hate reinventing the wheel, so I went Googling for a batch conversion tool. I looked for a couple of hours installing several candidates. None seemed batch very well, and all but a couple of sourceforge projects were loaded with extra free version garbage. I stumbled on to ffmpeg it is a very versatile command line based encoding tool. I must have been reading some bad or platforms specific examples because they all instructed the use of -acodec libmp3lame as an argument when encoding. This yielded an error indicating that the libmp3library was not a valid encoder. That bogged me down for a trying to find out why this seemingly ubiquitous library was missing or not loading properly. Turns out it was there all along I just needed to use -f mp3 and let the exe figure the rest out.

This is what I was able to put together with some help from a few other examples. I simply recurse through a directory structure and call the ffmpeg.exe using the Invoke-Expression cmdlet. The script example is targeted at .wma files, but it could easily be modified to convert any other format like .wav, .oog or any other ffmpeg supported format. This setup seems to be working well enough; I may consider using something similar to normalize the file tagging with musicbrainz Picard.

Make sure to backup the files you plan on changing before running any batch operation. This script deletes the original .wma file see the "Remove-Item" line. There is no error checking around the EXE call to make sure the conversion completed successfully and that the file is playable. Prior to building this script I staged my changes so I could play around to get the script right.

#Set the path to crawl
$path = 'C:\Documents and Settings\User\My Documents\My Music\Convert'
#The source or input file format
$from = '.wma'
#The encoding bit rate
$rate = '192k'
Get-ChildItem -Path:$path -Include:"*$from" -Recurse | ForEach-Object -Process: { 
        $file = $_.Name.Replace($_.Extension,'.mp3')
        $input = $_.FullName
        $output = $_.DirectoryName
        $output = "$output\$file"
#-i Input file path
#-id3v2_version Force id3 version so windows can see id3 tags
#-f Format is MP3
#-ab Bit rate
#-ar Frequency
# Output file path
#-y Overwrite the destination file without confirmation
        $arguments = "-i `"$input`" -id3v2_version 3 -f mp3 -ab $rate -ar 44100 `"$output`" -y"
        $ffmpeg = ".'C:\Program Files\ffmpeg\bin\ffmpeg.exe'"
        Invoke-Expression "$ffmpeg $arguments"
        Write-Host "$file converted to $output"
#Delete the old file when finished
#This could use some error checking around it to prevent accidental deletion.
        Remove-Item -Path:$_
    }