Montag, 22. Juni 2026

How to build perfect Home Directory on Netapp

This post collect some thoughts how to run the perfect  quoted "personal" shares for Windows Fatclients / Citrix / Horizon / FSLogix

What Model will you choose ?

How many Users do you have ?

10-100 : Keep it simple.
100-5000 : Low enough to scale in single instances.
5000+ : You need to scale to several instances.

Do you have several environments (Citrix Farms, huge departments with dedicated IT teams, several AD Domains) ?

No : Perfect, one to fit all is easier.  
Yes : Consider volume or qtree per SILO.

Now the KnowHow

classify your user into "use cases"

Instead of managing a quote for every user create qtrees for "Use Cases" and put a separate quota on it, instead of setting quotas you (better a 1st level agent) just copy the profile to another qtree. The homefolder search path feature will lead the user to the qtree where his profile is.


Unfortunatly most Citrix / Windows / FSLogic policy voodoo don't like the %USERNAME% variable as name for a share, but if you use a static share name Windows will say 2 users accessing the same UNC Path of a file will cause a lock, the trick to avoid this is to share recursivly the folder where the Folder %USERNAME% by adding /.. to the path ending with %w - %w/.. .

Use DFS-N to stay flexible and prevent pit fights about responsibilities

You don't want to coordinate with several departments changing paths in all their tools and policies, DFS-N will avoid you ALOT trouble and add some "benefit". 

The Problem with the search-pattern based share is that SOMEONE has to create the profile folder first before the share will even work for the user - but if creator != user the creator has to prepare the ACLs on the folder too. 

Just misuse DFS-N failover for the onboarding process. You build up a "low quota" search path - where every new user will start, just create a nonbrowseable welcome$ share on this path and define it as secondary target for the DFS-N Path. A fresh user trying to access the \\fileserver\home\ share will fallback to \fileserver\welcome$ and create his folder there, next time he access the DFS-N link the search paths will return the qtree with the lowest quota and succeed.

But be aware that if someone still use the classic Users and Computers Active Directory MMC the Path will created in his "Use Case" qtree and will has him as owner.  

Keep control over the structure (special folders / trashcans / language chaos)

TBD : Reg Keys for "Well-known Folders" and Recycle Bin - desktop.ini voodoo

MONITOR everything - really EVERYTHING

TBD : REST-API Links  

Setting self managing ACLs

This little function will set the right ACLs on all search pattern paths you define earlier.

function Set-ProfileFolderRoot {
    param(
        [Parameter(Mandatory = $True, Position = 1)]
        [ValidateScript( { $DirInfo = [System.IO.DirectoryInfo]"$_"; $DirInfo.Exists -and ($DirInfo.Mode -match '^d') -and ($DirInfo.FullName -ne $DirInfo.Root) })]
        [String]$ShareRootPath,
        [Parameter(Position = 2)][String[]]$ProfileRoot,
        [Parameter()][String]$AdminNTGroup = "DOMAIN\StorageAdmins"
    )


    If (-not $PSBoundParameters['ProfileRoot']) {
        $RootACL = [System.Security.AccessControl.DirectorySecurity]::new()
        $RootACL.AddAccessRule([System.Security.AccessControl.FileSystemAccessRule]::new([System.Security.Principal.SecurityIdentifier]"S-1-5-32-544",2032127,3,0,0))
        $RootACL.AddAccessRule([System.Security.AccessControl.FileSystemAccessRule]::new([System.Security.Principal.NTAccount]$AdminNTGroup,2032127,3,0,0))
        $RootACL.AddAccessRule( [System.Security.Principal.SecurityIdentifier]"S-1-1-0",131241,0,0,0)
        $RootACL.SetAccessRuleProtection(1, 0)
        [System.IO.Directory]::SetAccessControl($ShareRootPath, $RootACL)
        $ProfileRoot = ([System.IO.DirectoryInfo]$ShareRootPath).GetDirectories().Name
    }
    else {
        $ProfileRoot.Where( { -not $([System.IO.DirectoryInfo]"$($ShareRootPath.TrimEnd('\'))\$_").Exists }) | ForEach-Object {
            [System.IO.Directory]::CreateDirectory("$($ShareRootPath.TrimEnd('\'))\$_")
        }
    }

    $ProfileRoot.ForEach( {
            $ProfileACL = [System.Security.AccessControl.DirectorySecurity]::new()
            $ProfileACL.AddAccessRule([System.Security.AccessControl.FileSystemAccessRule]::new([System.Security.Principal.SecurityIdentifier]"S-1-5-32-544",2032127,3,0,0))
            $ProfileACL.AddAccessRule([System.Security.AccessControl.FileSystemAccessRule]::new([System.Security.Principal.NTAccount]$AdminNTGroup,2032127,3,0,0))
            $ProfileACL.AddAccessRule([System.Security.AccessControl.FileSystemAccessRule]::new([System.Security.Principal.SecurityIdentifier]"S-1-3-0",197055,3,0,0))
            $ProfileACL.AddAccessRule([System.Security.AccessControl.FileSystemAccessRule]::new([System.Security.Principal.SecurityIdentifier]"S-1-1-0",131245,0,0,0))
            $ProfileACL.SetAccessRuleProtection(1, 0)
            [System.IO.Directory]::SetAccessControl("$($ShareRootPath.TrimEnd('\'))\$_", $ProfileACL)
        } )

}

Montag, 4. Dezember 2023

Dealing with "the authorization policy does not allow saved credentials" in RoyalTS

Having to type your password when connecting to RDP suxx, i regular use RoyalTS as RDP client which is even free for personal use but limited to 10 connections (not a hard one, use dynamic folders and clear them before save).

To deal with nasty PW prompts i wrote a script which integrates a task named "UseSavedCred" to your "Main" document usable as "Connect Task". 

Save it as PS1 and run it, if RoyalTS is running it stop it, and restart it after the install process.

Now you can choose the Task for "Connect Task" for the RDP Connections which pop up the credential window.




The "installed" script search in the sub processes of it an editable password field and type the password in it + press Enter. I did not test it with passwords with "<",">","|" or "&" character inside, as they often brocke sending them selves through the pipeline. 

Give me feedback into the comments.

Dienstag, 18. Januar 2022

Quick-n-dirty function to test a NTP Server per Powershell

.. quite simple - just to test if a ntp server works, if you need more accuracy than +/-9sec just match against ",\ (\+|-)00" 

function Test-NTP($ntpserver){
$pinfo=[System.Diagnostics.ProcessStartInfo]::new("$($env:SystemRoot)\system32\w32tm.exe",@("/stripchart","/computer:$ntpserver","/dataonly","/samples:1"))
$pinfo.RedirectStandardOutput = $true
$pinfo.UseShellExecute = $false
$ntptestproc=[System.Diagnostics.Process]::new()
$ntptestproc.StartInfo=$pinfo
$ntptestproc.Start()|Out-Null
$ntptestproc.WaitForExit()
return $ntptestproc.StandardOutput.ReadToEnd() -match ",\ (\+|-)0"
}

Dienstag, 7. September 2021

Loseless Merging Directories with Powershell

 

I needed a nice little function for merging directories without loosing documents in case of collisions of paths. Because i needed this for tidying up a hell of messed up homedrives i didnt care about any ACL etc.

function MoveCollisionFree([string]$srcdir,[string]$dstdir){
$srcdir = $srcdir.TrimEnd('\')
$dstdir = $dstdir.TrimEnd('\')
$srcRegEx=[regex]::Escape($srcdir)

[System.IO.Directory]::GetDirectories($srcdir).ForEach({
$DestPath=$_ -replace($srcRegEx,$dstdir)
if([System.IO.Directory]::Exists($DestPath)){
MoveCollisionFree -srcdir $_ -dstdir $DestPath
}else{[System.IO.Directory]::Move($_,$DestPath)}
})
[System.IO.DirectoryInfo]::new($srcdir).GetFiles().ForEach({
$DestFile=[System.IO.FileInfo]::new($($_.FullName -replace($srcRegEx,$dstdir)))
if($DestFile.Exists){
if($DestFile.LastWriteTime -ge $_.LastWriteTime){
$_.MoveTo("$($DestFile.Directory.FullName)\$($_.BaseName)_older_from_$($_.LastWriteTime.ToString('yyyy-MM-dd'))$($_.Extension)")
}else{
$NewFileName=$DestFile.FullName
$DestFile.MoveTo("$($DestFile.Directory.FullName)\$($_.BaseName)_older_from_$($DestFile.LastWriteTime.ToString('yyyy-MM-dd'))$($_.Extension)")
$_.MoveTo($NewFileName)
}
}else{$_.MoveTo($DestFile.FullName)}
})
Remove-Item $srcdir -Recurse -Force -Confirm:$false
}

Donnerstag, 22. Oktober 2020

How to extend NABox with Capacity information without having OCUM - my first python


Monitoring a Netapp is important, the "unofficial" Monitoring NAbox is installed like a slice of cake.


If you never heard about it - check it out here .. https://nabox.org/

But it does not collect the "capacity" information of his objects (volumes and aggregates) without the help of a OCUM (or Active IQ Unified Manager) . 

Instead of installing another Appliance you can collect the needed data from Netapp Systems running Ontap 9.6+ by NAbox itself per REST-API.

First create a authentification certificate (which is usable for Powershell too, see my next post) : 

Log into the NABox with SSH - (root:NetappGrafanaVA are the default login credentials). 

mkdir /opt/myontapcol/

openssl req -x509 -nodes -days 3650 -newkey rsa:2048 -keyout /opt/myontapcol/ontapkey.key -out /opt/myontapcol/ontap.pem -subj "/C=DE/ST=WONDERLAND/L=EMERALDCITY/O=IT/CN=nabox"

BE AWARE OF "CN=nabox" this will be the user which we configure in  the next step.

cat /opt/myontapcol/ontap.pem

connect with a 2nd SSH Shell to the Netapp and run this command, take the output from above into clipboard, you need to paste it.

security certificate install -type client-ca -vserver mycluster

it sometimes needs 3 "enter".

security ssl modify -client-enabled true -vserver mycluster

security login create -user-or-group-name nabox -application http -authentication-method cert -role readonly -vserver mycluster

your netapp answers now REST API requests,

You can test it on the nabox shell with

curl -s --key /opt/myontapcol/ontap.key --cert /opt/myontapcol/ontap.pem -k https://ontap-a.acme.corp/api/storage/aggregates

Now lets a script feed the NABox with data, except the python extension jsonpickle the nabox have all neccesary things allready installed.

pip install -U jsonpickle

I uploaded the script here .. naboxcapacol unzip the file open up the naboxcapacol.py file with notepad.

[UPDATE - Newer NA Boxes do not use the port 2004 and use 2013 for pickles counter - find the config file with ps -au|grep carbon and grep -A 5 ^.pick /etc/go-carbon/go-carbon.conf  ]


Copy the script into the file and save it.

nano /opt/myontapcol/ontapcapacol.py

#make it executable

chmod +x /opt/myontapcol/ontapcapacol.py

#and let it run every 5min

crontab -e

*/5 * * * * /opt/myontapcol/ontapcapacol.py >> ~/cron.log 2>&1

# after 1h take a look for some of the data 

https://nabox/graphite/?width=800&lineMode=connected&showTarget=mgtechhead.*.*.aggregates.*.volumes.*.used&height=600&target=mgtechhead.*.*.aggregates.*.volumes.*.used

You can now import my "simple" capacity dashboard ontapcapacol.json from the zip or just create your own. I created this for a customer who is not using so much qtrees.

The dashboards which came with harvest are not connecting volume and aggregate together, if you want to have the data here you have to alter the script in a way that it pumps the data to this paths.

netapp/capacity/$Group/$Cluster/svm/$SVM/vol/$Volume
netapp/capacity/$Group/$Cluster/svm/$SVM/vol/$Volume/afs_total
netapp/capacity/$Group/$Cluster/svm/$SVM/vol/$Volume/snapshot_reserve_total
netapp/capacity/$Group/$Cluster/svm/$SVM/vol/$Volume/snapshot_used_percent
netapp/capacity/$Group/$Cluster/svm/$SVM/vol/$Volume/afs_avail
netapp/capacity/$Group/$Cluster/svm/$SVM/vol/$Volume/afs_daily_growth_rate
netapp/capacity/$Group/$Cluster/svm/$SVM/vol/$Volume/overwrite_reserve_total
netapp/capacity/$Group/$Cluster/svm/$SVM/vol/$Volume/snapshot_reserve_avail
netapp/capacity/$Group/$Cluster/svm/$SVM/vol/$Volume/afs_used_percent
netapp/capacity/$Group/$Cluster/svm/$SVM/vol/$Volume/overwrite_reserve_used
netapp/capacity/$Group/$Cluster/svm/$SVM/vol/$Volume/overwrite_reserve_avail
netapp/capacity/$Group/$Cluster/svm/$SVM/vol/$Volume/actual_volume_size
netapp/capacity/$Group/$Cluster/svm/$SVM/vol/$Volume/afs_used_per_day
netapp/capacity/$Group/$Cluster/svm/$SVM/vol/$Volume/snapshot_reserve_used
netapp/capacity/$Group/$Cluster/svm/$SVM/vol/$Volume/total
netapp/capacity/$Group/$Cluster/svm/$SVM/vol/$Volume/afs_used
netapp/capacity/$Group/$Cluster/svm/$SVM/vol/$Volume/quota_committed_space
netapp/capacity/$Group/$Cluster/node/$Node/aggr/aggr_$Aggregate
netapp/capacity/$Group/$Cluster/node/$Node/aggr/aggr_$Aggregate/compression_space_savings
netapp/capacity/$Group/$Cluster/node/$Node/aggr/aggr_$Aggregate/dedupe_space_savings
netapp/capacity/$Group/$Cluster/node/$Node/aggr/aggr_$Aggregate/snapshot_reserve_total
netapp/capacity/$Group/$Cluster/node/$Node/aggr/aggr_$Aggregate/size_used
netapp/capacity/$Group/$Cluster/node/$Node/aggr/aggr_$Aggregate/size_used_per_day
netapp/capacity/$Group/$Cluster/node/$Node/aggr/aggr_$Aggregate/size_total
netapp/capacity/$Group/$Cluster/node/$Node/aggr/aggr_$Aggregate/size_available
netapp/capacity/$Group/$Cluster/node/$Node/aggr/aggr_$Aggregate/daily_growth_rate
netapp/capacity/$Group/$Cluster/node/$Node/aggr/aggr_$Aggregate/snapshot_reserve_avail
netapp/capacity/$Group/$Cluster/node/$Node/aggr/aggr_$Aggregate/compression_space_savings_percent
netapp/capacity/$Group/$Cluster/node/$Node/aggr/aggr_$Aggregate/dedupe_space_savings_percent
netapp/capacity/$Group/$Cluster/node/$Node/aggr/aggr_$Aggregate/space_total_committed
netapp/capacity/$Group/$Cluster/node/$Node/aggr/aggr_$Aggregate/size_used_percent
netapp/capacity/$Group/$Cluster/node/$Node/aggr/aggr_$Aggregate/snapshot_reserve_used


Known issue - if you move a volume you have to delete the counters 

rm /opt/graphite/storage/whisper/mgtechhead/*/*/aggregates.[former_aggregate].volumes.[volume_name]/*
rmdir /opt/graphite/storage/whisper/mgtechhead/*/*/aggregates.[former_aggregate].volumes.[volume_name]

Freitag, 1. November 2019

Add a Netapp LUN as VM Datastore per Powershell

You can speak Powershell to Netapp and VMware, so adding a LUN as Datastore should not a problem, despite the fact that Netapp Powershell command get-nclun does not deliver the worldwidename of the lun per default.

But according this KB from Netapp a LUN NAA is just a prefix + serial ascii to hex converted.

https://kb.netapp.com/app/answers/answer_view/a_id/1033595/~/how-to-match-a-luns-naa-number-to-its-serial-number-

This is "stupid" adding all LUNs as Datastore using the filename of the LUN as Suffix.

Get-NcLun|select Node,Vserver,Path,
 @{N='NAA';E={"naa.600a0980$(-join ($_.SerialNumber.ToCharArray()|%{'{0:x}' -f $([byte][char]$_)}))"}}|%{
  get-vmhost esxserver|New-Datastore -Vmfs -Name "VMFS_$($_.Path.Split('/')[2])" -Path $_.NAA
  }

Maybe better declaring a function to make Code better readable ...

function Convert-ASCIItoHEX_STRING ([string]$inputstring){
return $(-join ($inputstring.ToCharArray()|%{'{0:x}' -f $([byte][char]$_)}))
}

Get-NcLun|select Node,Vserver,Path,
 @{N='NAA';E={"naa.600a0980$(Convert-ASCIItoHEX_STRING $_.SerialNumber)"}}|%{
  get-vmhost esxserver|New-Datastore -Vmfs -Name "VMFS_$($_.Path.Split('/')[2])" -Path $_.NAA
  }

I hope this comes handy sometimes ..

Mittwoch, 4. Juli 2018

How to recover access to a Brocade SAN switch after password dementia.

There are already too much blog posts around ? Most of them just talk about going onsite - using serial cable or "back door" user which are mostly disabled.

When the default password 'fibranne' for the root user did not help you, but you have still access to another switch in the fabric, this ist the way to push the password database from one switch you can access to the one you locked yourself out.

distribute -p PWD -d [#SwitchID]

#SwitchID is the decimal number in the first row in fabricshow output.

After this you can logon with the same password you know from switch A.

This is also a fine way to prevent to setup every password for admin, user, factory etc. by hand. Just setup one switch in the fabric properly and "push" the settings to each - or to configure fresh passwords FAST in a environment.