Apply category rules to all files

You may have the need for applying the category rules to all of your files within the Vault. Maybe you moved from vanilla Vault to Vault Workgroup, Collaboration or Professional. Or maybe you just uploaded all your files via Autoloader or similar, and now wonder how to set the right category to your files. Or you changed some rules, and like now to apply them to all files. If you try to do it manually, it might take very long, or you might reach a limit. And if you do it in steps, then you may end having a little triangle while changing the state, that tells you the assembly does not point to the latest children, and you have to update the assemblies manually. To solve this issue quite simply, here comes a little script.

It’s again in PowerShell, so ready to use on every Windows 7 machine, but of course you can take the syntax and bring it into your favorite language.

We start by loading the WebServices.DLL and then log in. If you are new to this, you may want to have a look to this post first: querying the Vault database

The following code basically search for all files under the root folder  ($/) and recursively applies the category based on the rules you set. So, before you start using the script, configure your category rules in Vault. We use the search instead of walking folder by folder as it is the most performing way for getting lots of data from the Vault (read this blog from Doug Redmond). In order to let the assemblies consume the latest version of the child files, the script works recursively, from bottm-up, and checks the files out and in with latest versions of the references. In order to avoid redoing the same task on multiple used files, the script keeps a list of already processed files, and skips those files that has been already treated. Additionally, the script skips the file if the category is already set. It also skips files that are checked-out (the category cannot be changed) and writes it out into a log file. There is no check for permissions, so make sure your user has really all needed permissions.

#region login to Vault
[System.Reflection.Assembly]::LoadFrom("C:\Program Files (x86)\Autodesk\Autodesk Vault 2013 SDK\bin\Autodesk.Connectivity.WebServices.dll")

$cred = New-Object Autodesk.Connectivity.WebServicesTools.UserPasswordCredentials("localhost","Vault","Administrator","")
$vault = New-Object Autodesk.Connectivity.WebServicesTools.WebServiceManager($cred)
#endregion

$logFile = "c:\temp\cOupdateVault.log"
if(Test-Path $global:logFile) { Remove-Item $global:logFile -Force }

function applyCategory
{ param($file)

	if($global:processedFiles.Keys -contains $file.MasterId) { continue; } #if file already processed, then skip

#region get children in order to recurse from bottom up
	$assocs = $vault.DocumentService.GetFileAssociationsByIds(@($file.Id), [Autodesk.Connectivity.WebServices.FileAssociationTypeEnum]::None, $false, [autodesk.Connectivity.WebServices.FileAssociationTypeEnum]::All, $false, $false, $false)
	$assocs = $assocs[0].FileAssocs #as we request assocs just for one file, there could be just one element
	if($assocs -ne $null)
	{
		foreach($assoc in $assocs) {
			Write-Progress -Activity "Processing children" -Status $assoc.CldFile.Name -Id 1
			applyCategory $assoc.CldFile #recurse the children
		}
		Write-Progress -Activity "Processing children" -Status "done" -Id 1
	}
#endregion

	$catIds = $vault.CategoryService.ApplyCategoryRules(@($file.MasterId)) #get the applicable category
	if($file.CheckedOut)
	{
		"File $($file.Name)) is checked out!" | Out-File $logFile -Append
		return
	}
	$newFile = $file #store the current file in case the category is already set
	if($file.Cat.CatId -ne $catIds[0]) #if the applicable category is different from the current, then apply
	{
#region relink file associations to latest version
		if($assocs -ne $null) #if there are dependencies, then reset them to the latest version
		{
			$associations = New-Object Autodesk.Connectivity.WebServices.FileAssocParam[] $assocs.Count
			for ($index = 0; $index -lt $assocs.Count; $index++) {
				if($global:processedFiles.Keys -notcontains $assocs[$index].CldFile.MasterId) { return } #if the master ID is uknown, something with a child went wrong, so get back
				$associations[$index] = New-Object Autodesk.Connectivity.WebServices.FileAssocParam
				$associations[$index].CldFileId = $global:processedFiles[$assocs[$index].CldFile.MasterId] #set the child to the latest ID
				$associations[$index].Source = $assocs[$index].Source
				$associations[$index].Typ = $assocs[$index].Typ
				$associations[$index].ExpectedVaultPath = $assocs[$index].ExpectedVaultPath
				$associations[$index].RefId = $assocs[$index].RefId
			}
			$folder = $vault.DocumentService.GetFoldersByFileMasterId($file.MasterId) #get the folder for the file
			$buffer = New-Object Autodesk.Connectivity.WebServices.ByteArray
			$newFile = $vault.DocumentService.CheckoutFile($folder[0].Id, $file.Id, [Autodesk.Connectivity.WebServices.CheckoutFileOptions]::Master, [System.Environment]::MachineName, "c:\temp\script", "resolving links", [Autodesk.Connectivity.WebServices.DownloadOptions]::No, $true, [ref] $buffer)
			$newFile = $vault.DocumentService.CheckinFile($file.MasterId, "links resolved", $false, [DateTime]::Now, $associations, $null, $true, $file.Name, $file.FileClass, $file.Hidden, $buffer)
		}
#endregion
		$newFiles = $vault.DocumentServiceExtensions.UpdateFileCategories(@($file.MasterId),$catIds, "Category applied") #update the category
		$newFile = $newFiles[0]
	}
	$global:processedFiles[$file.MasterId] = $newFile.Id #remember the new file ID for the current file
}

$srcConds = New-Object Autodesk.Connectivity.WebServices.SrchCond[] 0
$rootFolder = $vault.DocumentService.GetFolderRoot()
$bookmark = $null
$status = $null
$files = @()
$counter = 0
$global:processedFiles = @{} #remember files' new ID of already processed files
while ($status -eq $null -or $counter -lt $status.TotalHits )
{
	Write-Progress -Activity "Collecting files" -Status "please wait..." -Id 0
	$files = $vault.DocumentService.FindFilesBySearchConditions($srcConds, $null, @($rootFolder.Id), $true, $true, [ref] $bookmark, [ref] $status)

	foreach ($file in $files) #enumerate all files
	{
		Write-Progress -Activity "Apply category" -Status $file.Name -PercentComplete ($counter++ *100/$status.TotalHits) -Id 0
		if($global:processedFiles.Keys -contains $file.MasterId) { continue; } #if file already processed, then skip
		applyCategory $file
	}
}

You can run this script multiple time. It will only update those files which are not in the right category yet. Of course this script can be adapted to your needs. For instance you can limit the action zone to a specific folder, or put some more filters on.
Variations of this script might also set the revisions and the lifecycle to your files.

ATTENTION! This script will make sure that the latest version of child elements will be consumed by the parent object (assembly, drawing, etc.). Be aware that all your files will then point to the latest version of the children.

I hope you enjoy this script, and as usual, feedback is welcome.

This entry was posted in PowerShell, Vault API. Bookmark the permalink.

3 Responses to Apply category rules to all files

  1. Polybius says:

    Can this be tweaked to work in 2014? I had it working in 2013 and was very valuable.

    • Marco Mirandola says:

      Sure!! As 2015 beta is available, I will see to have it running on both. I’ll get back to you asap. Btw, we are currently developing a Vault app called vaultRuler, which will do this and more. should be soon available, so might be a good alternative.

      • Polybius says:

        Thanks Marco! Yeah I might consider vaultRuler, but I usually only need to change the categories once or twice during the year and once before we migrate to the next version of Vault. But I can’t tell you how much time it saves!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s