Migrating from NetApp to Windows File Servers with PowerShell – part 3

In our old NetApp environment we utilized quotas extensively. We want to map our NetApp quotas to Windows File System Resource Manager quotas in a failrly seamless fashion. Fortunately, with PowerShell, this is almost easy. Microsoft gives us the FSRM.FsrmQuotaManager and FSRM.FsrmQuotaTemplateManager “COM” object classes, documented here:
PowerShell works with these with ease.

I got a jump start on these scripts from my brethren on the Internet. Perhaps the most helpful post was Ben Lye’s entry on “Simple-talk”:

First task… create FSRM quota templates that are similar to our NetApp qtree quotas. We don’t need to use “templates”, but it makes management a bit easier, so we will:

#Creates simple hard-limit directory quotas of the GB values specified in $tholds.

Set-PSDebug -Strict
$qtm = New-Object -com Fsrm.FsrmQuotaTemplateManager

[int[]] $tholds = 25,50,100,150,200,300,400,600,800,1200

foreach ($thold in $tholds) {
	$templ = $qtm.CreateTemplate()
	[string] $tName = [string]$($thold) + " Gb Shared Directory"
	$templ.Name = $tName
	[long] $tLimit = $thold * 1073741824
	$templ.QuotaLimit = $tLimit
	Clear-Variable templ
	Remove-Variable templ

Now we want to map our NetApp quotas to the new FSRM quotas. Challenges here are:

  1. extracting structured data from the NetApp “quotas” file.
  2. mapping NetApp quotas to the nearest-larger FSRM quota template.

The first challenge is handled using the .NET RegEx and String classes. It was a hack job, but it worked.

The second challege was handled using the powershell “switch” command. The one roadblock I ran into here was caused by a failure to cast a variable as the appropriate data type. For those who did not already know, you never, uh, I mean always should cast your valiables. Why? Look at the switch in the code below… what do you think happens when you compare the un-cast pipeline variable ($_) to “100”? Did you answer “geez dude, I don’t know, that’s a gibberish.”? Join the club. Did you answer “PowerShell does a string comparison, dude! $outQuota was an array of strings! What the heck were you thinking? Do you want 1000 to be considered a larger value than 25?”? If so, then you are correct. If you want to do mathematical comparison, you must cast your valiables an numbers. And by the way, do not cast large numbers as “[int]”. Why? Because [int] will not exceed 32-bits in length. Use [long] or [double] instead.

# Applies quota templates to s:shared subdirectories, mapped from input file
# specified in $inQuota (the NetApp quotas file)
# NetApp quota limits are matched to the closest larger Windows quota template.

Set-PSDebug -Strict

#parse NetApp quota file:
$inQuota = Get-Content '\filesetc$quotas'
$outQuota = @()
#following loop strips all but the shared directory name and hard limit from input:
foreach ($line in $inQuota) {
	$arrLine = @()
	if ($line -match "/vol/vol2/") {
	[string] $ind0 = [regex]::split($line," tree ") | Select-Object -Index 0 |`
		% {$_.trim()} | % {$_.replace("/vol/vol2/", "")} | % {$_.replace('"', '')} 
	[string] $ind1 = [regex]::split($line," tree ") | Select-Object -Index 1 |`
		% {$_.split("G")} | select-object -index 0 | % {$_.trim()}
	$outQuota += ,@($ind0,$ind1)
Remove-Variable inQuota

#instantiate quota manager:
$qm = New-Object -com Fsrm.FsrmQuotaManager

foreach ($quota in $outQuota) {
	#determine Windows Quota Template that is closest to NetApp quota:
	[long] $qVal = $quota[1]
	switch ($qVal) {
		{$_ -le 1200} {[string]$qtl = "1200"}
		{$_ -le 800} {[string]$qtl = "800"}
		{$_ -le 600} {[string]$qtl = "600"}
		{$_ -le 400} {[string]$qtl = "400"}
		{$_ -le 300} {[string]$qtl = "300"}
		{$_ -le 200} {[string]$qtl = "200"}
		{$_ -le 150} {[string]$qtl = "150"}
		{$_ -le 100} {[string]$qtl = "100"}
		{$_ -le 50} {[string]$qtl = "50"}
		{$_ -le 25} {[string]$qtl = "auto"}
	# values 25 or smaller use an autoapply template, so skip these...
	if ($qtl -notmatch "auto") { 
		# create a quota object by retrieving an existing auto-apply quota, then update:
		[string] $deptDir = 's:shared' + $quota[0]
		$qObj = $qm.GetQuota($deptDir)
		[string] $templ = $qtl + " Gb Shared Directory"
		Clear-Variable qObj
		Remove-Variable qObj

Now moving on to user home directory quotas… we used “AutoApplyTemplates” for this situation. AutoApply templates force afixed quota onto all new subdirectories of a parent. In this way, we avoid having to set a template manually for every new user home directory. Our file server contains six volumes (h:-m:), each containing a “homes1” subdirectory, each of which in turn contins subdirectories 0-9, a-z. Individual home directories are nested within these. We needed to apply AutoApplyTemplates to these 0-9,a-z directories. Easy…

# Applies an Auto-Apply quota to all directories at the second directory level 
# of the volumes specified in $homeVols.

Set-PSDebug -Strict

[string[]] $homeVols = @()
$homeVols = "h:","i:","j:","k:","l:","m:"

$qm = New-Object -com Fsrm.FsrmQuotaManager

foreach ($vol in $homeVols) {
    $subDirs1 = gci $vol | ? {$_.Attributes.tostring() -match "Directory"}
    foreach ($dir1 in $subDirs1) {
        [array] $subDirs2 = gci $dir1.FullName | ? {$_.Attributes.tostring() -match "Directory"}
        if ($subDirs2.count -ge 1) { foreach ($dir2 in $subDirs2) {
            [string] $uPath = ($vol + $dir1.name + '' + $dir2.name)
			[string] $template = "10 Gb Home Directory"
            $quota = $qm.CreateAutoApplyQuota($template,$uPath)
            clear-variable quota
            remove-variable quota
        }} #End if ($subDirs2.count -ge 1)
    } #End $dir1 in $subDirs1
} #End $vol in $homeVols

On the NetApp, we had a single volume that hosted users with a larger home directory quota. Under Windows, we can simply set a different directory quota for these individuals which overrides the AutoApplyTemplate. I have done this for the existing 25Gb quota users already, using PowerShell:

# Applies a new quota to the third-level directory of the volumes specified in $homeVols, 
#  under the path $homeVol:homes2[a-z][samAccountName]

Set-PSDebug -Strict

[string[]] $homeVols = @()
$homeVols = "n:","m:"

$qm = New-Object -com Fsrm.FsrmQuotaManager
$qtm = New-Object -com Fsrm.FsrmQuotaTemplateManager

foreach ($vol in $homeVols) {
    [string] $dir1 = $vol + "homes1"
    $subDirs2 = gci $dir1 | ? {$_.Attributes.tostring() -match "Directory"}
    foreach ($dir2 in $subDirs2) {
        [array] $uhomes = gci $dir2.FullName | ? {$_.Attributes.tostring() -match "Directory"}
        if ($uhomes.count -ge 1) { foreach ($uhome in $uhomes) {
            [string] $uHomePath = ($dir1 + '' + $dir2.name + '' + $uhome.name)
            $quota = $qm.GetQuota($uHomePath)
            $quota.ApplyTemplate("25 Gb Home Directory")
            clear-variable quota
            remove-variable quota
        }} #End if ($uhomes.count -ge 1)
    } #End $dir2 in $subDirs2

} #End $vol in $homeVols

And that is all I have for quotas for now. We will need to enhance our templates with threshold alerts and notifications in the future. This could prove interesting as the IFsrmObject.IFsrmQuotaBase class looks a bit challenging to deal with: