If running SharePoint on Windows 2003, one just needs to install Powershell components.
Thanks go out to http://www.astaticstate.com/2007/10/copy-version-history-with_5.html. My implementation adapted from their code.
### CHANGE ME ########################### # Expiration date # $expireDate = (Get-Date).AddDays(-365) # Base site URL # $spSiteUrl="http://path/to/sharepoint/site" # Source and destination list URLS $srcListUrl=$spSiteUrl+"/Lists/Request" $dstListUrl=$spSiteUrl+"/Lists/Archive Request" ######################################### [System.Reflection.Assembly]::Load(“Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c”) [System.Reflection.Assembly]::Load(“Microsoft.SharePoint.Portal, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c”) # Connect to sharepoint site $spSite=[Microsoft.SharePoint.SPSite]($spSiteUrl) $spWeb=$spSite.OpenWeb(); # Get lists $srcList=$spWeb.GetList($srcListUrl) $dstList=$spWeb.GetList($dstListUrl) # Get list items $srcListItems = [Microsoft.Sharepoint.SPListItemCollection]($srcList.Items) $dstListItems = [Microsoft.Sharepoint.SPListItemCollection]($dstList.Items) $toArchive = 0 $toKeep = 0 $purgeIds = @() # Preflight Write "Calculating damage..." foreach ($srcItem in $srcListItems){ # Must not be younger than the expiration date or opened if($srcItem["Modified"] -gt $expireDate -or $srcItem["Request Status"] -ne "Closed"){ $toKeep++ continue } $purgeIds += $srcItem["ID"] $toArchive++ } Write-Host To Keep: $toKeep Write-Host To Archive: $toArchive Write-Host "Here we go..." # Loop through candidates for ($s = 0; $s -lt $purgeIds.Count; $s++){ $srcItem = $srcListItems.GetItemById($purgeIds[$s]) # Must not be younger that the expiration date or opened if($srcItem["Modified"] -gt $expireDate -or $srcItem["Request Status"] -ne "Closed"){ continue } # Dump the item name for later cleaning Write-Host Archiving '{' $srcItem["ID"] - $srcItem["Name"] - $srcItem["Title"] '}' # Add destination list item $newItem = $dstListItems.Add() # We must loop through each item version and create at destination for ($i=$srcItem.Versions.Count - 1; $i -ge 0; $i--) { # Loop through each field foreach ($srcField in $srcItem.Fields) { $version = $srcItem.Versions[$i] if ($srcField.ReadOnlyField -ne $True -and $srcField.InternalName -ne "Attachments") { $newItem[$srcField.Title] = $version[$srcField.Title] } elseif ($srcField.Title -eq "Created" -or $srcField.Title -eq "Created By" -or $srcField.Title -eq "Modified" -or $srcField.Title -eq "Modified By") { $newItem[$srcField.Title] = $version[$srcField.Title] } } # Update item for next version $newItem.Update() } # Whack the attachments to the list item foreach($baseName in $srcItem.Attachments){ $file = $srcList.ParentWeb.GetFile($srcItem.Attachments.UrlPrefix + $baseName) $newItem.Attachments.Add($baseName, $file.OpenBinary()) } # And another update $newItem.Update() # Purge the source $srcItem.Delete() } # Move onto next candidate