Faciliter sa migration de fileserver DFS avec Powershell

Ayant éprouvé ces scripts lors d'une migration d'un serveur de fichiers, je vais les partager dans ce billet. Le but n'est pas d'automatiser complètement la migration mais de faciliter le travail et de limiter les risques d'erreurs humaines.

A noter qu'il est important de vérifier le bon fonctionnement des scripts sur votre environnement : en effet, il est possible que les espaces dans les noms de chemin pour les scripts Robocopy puissent causer des dysfonctionnements. Il est également important que les noms de répertoires sur le serveur correspondent au nom des répertoires DFS. Il peut donc être nécessaire d'adapter le code pour qu'il fonctionne correctement.

Ce snippet appelle Robocopy pour faire le transfert des données.

$servername = "fs001.dundermifflin.inc"
cd c:\mig\robocopy
$FoldersToDo = Get-Item \\$ServerName\C$\shares\* | select name
foreach($Folder in $FoldersToDo){
    $FolderName = $Folder.Name
	start-process "robocopy.exe" -ArgumentList "/MIR /COPYALL /ZB /W:0 /R:0 /MT:16 \\$ServerName\C$\shares\$FolderName C:\shares\$FolderName /log:robocopy_$ServerName_$FolderName.log /v" -wait
}

Ce script va s'occuper de la partie DFS ; il va ajouter le nouveau serveur de fichier en tant que répertoire cible pour chaque répertoire DFS qui contient un lien vers l'ancien serveur. Ce nouveau lien sera en désactivé, ainsi la migration peut être préparée en amont.

Param([Parameter(Mandatory=$true)][string] $OldServer, [Parameter(Mandatory=$true)][string] $NewServer, [Parameter(Mandatory=$true)][string] $Namespace)
$fNameSpace = "$Namespace\*"
$folderlist = Get-DfsnFolder $fNameSpace | foreach { (Get-DfsnFolderTarget $_.Path).TargetPath } | Where-Object { $_ -like "\\$OldServer*" }
foreach($folder in $folderlist){
	$folderName = $folder.SubString($OldServer.Length+3,$folder.Length-$OldServer.Length-3)
    echo $folderName
	New-DfsnFolderTarget -Path "$Namespace\$folderName" -TargetPath "\\$NewServer\$folderName" -State Offline
}

Par exemple, si l'on souhaite migrer de fs001 vers fs002 tous les liens DFS du namespace Documents, le script pourra être appelé de cette manière :

.\ImportationDFS-NewServer.ps1 -OldServer fs001 -NewServer fs002 -Namespace \\dundermifflin.inc\Documents

Ainsi, le jour de la migration, en jouant ce script on réalise la bascule vers le nouveau serveur fs002 puisque celui-ci va activer les liens DFS vers le nouveau serveur en désactivant ceux de l'ancien serveur.

Param([Parameter(Mandatory=$true)][string] $OldServer, [Parameter(Mandatory=$true)][string] $NewServer, [Parameter(Mandatory=$true)][string] $Namespace)
$fNameSpace = "$Namespace\*"
$folderlist = Get-DfsnFolder $fNameSpace | foreach { (Get-DfsnFolderTarget $_.Path).TargetPath } | Where-Object { $_ -like "\\$OldServer*" }
foreach($folder in $folderlist){
	$folderName = $folder.SubString($OldServer.Length+3,$folder.Length-$OldServer.Length-3)
    echo $folderName
	Set-DfsnFolderTarget -Path "$Namespace\$folderName" -TargetPath "\\$NewServer\$folderName" -State Online
    Set-DfsnFolderTarget -Path "$Namespace\$folderName" -TargetPath "\\$OldServer\$folderName" -State Offline
}

Ce script prend les mêmes paramètres que le précédent. En fonction du type de migration effectuée, il peut-être nécessaire de jouer le script plusieurs fois : si le nouveau serveur remplace et conserve le nom de l'ancien, il y a de grandes chances qu'à un moment le nouveau serveur doive être renommé. Dans ce cas, Microsoft conseille de supprimer le répertoire de destination dans DFS avant de renommer le serveur ; cependant si un répertoire DFS n'a plus de répertoire de destination, alors ce répertoire DFS est supprimé (en conservant bien sûr, les données sur le disque du serveur en question). Il sera donc nécessaire de procéder en plusieurs étapes et en jouant le script plusieurs fois afin de toujours conserver un lien DFS, qu'il soit actif ou non.

Par exemple, si l'on considère le serveur fs001 et le serveur fs001new, en admettant que l'on renomme le serveur fs001 en fs001old une fois la migration effectuée, cela nous donne ceci :

Etape #Serveurs contenus dans les liens DFSDétails
1fs001Etat pré-migration
2fs001 : actif - fs001new : inactifAjout de fs001new
3fs001 : inactif - fs001new : actifActivation de fs001new
Désactivation de fs001
4fs001new : actifRetrait de fs001
Renommage en fs001old
5fs001new : actif - fs001old : inactifAjout de fs001old
6fs001old : inactifRetrait de fs001new
Renommage en fs001
7fs001 : actif - fs001old : inactifAjout de fs001
8fs001Etat post-migration

Il n'y a pas de dysfonctionnement grave à ne pas retirer le serveur des répertoires cibles avant qu'il soit renommé. Il faudra cependant attendre une petite trentaine de minutes que côté AD tous les liens DFS soit remis en état par le système car bien qu'il s'agisse toujours de fs001, l'objet dans l'annuaire n'est pas le même. Il ne reste ensuite plus qu'à valider le bon fonctionnement.

Répertoires DFS visibles sans autorisation d'accès

J'ai été confronté à un problème provenant de la bureautique. Des utilisateurs voyaient dans leur liste de répertoires partagés sous Windows des partages DFS auxquels ils n'avaient pas accès, en plus de leurs répertoires DFS usuels. Normalement, Windows n'affiche pas de répertoire partagé si l'utilisateur n'a pas d'accès en lecture. Par exemple, si l'on considère la racine \\dundermifflin.inc\ et 3 répertoires DFS Sales, Accounting et Boss, si l'utilisateur n'a accès qu'au dernier il ne peut pas voir les dossiers Sales et Accounting.

Or dans mon cas, ces répertoires étaient bien visibles. La cause provient de la manière dont les permissions ont été positionnées dans la console DFS. Lors de la création d'un répertoire DFS, il est possible de positionner les privilèges de deux manières : en utilisant soit les permissions NTFS, soit DFS.

En choisissant l'option Use inherited permissions from the local file system, Windows ne va pas appliquer de permissions DFS sur le répertoire. Ainsi, comme son nom l'indique, les droits seront les mêmes que ceux positionnés sur le serveur qui héberge le répertoire en question. C'est justement ce réglage qui déclenche l'affichage dans l'explorateur. N'ayant aucune permission DFS de configurée, Windows ne sait pas avant d'interroger le serveur qui porte le répertoire si l'utilisateur a le droit de se connecter au non ; le répertoire est donc affiché. Ce n'est que lorsque l'utilisateur tente d'y accéder que Windows va récupérer les ACL et empêcher l'accès. Le comportement est par ailleurs expliqué dans la fenêtre ci-dessus.

La deuxième option, Set explicit view permissions on the DFS folder, place donc des permissions DFS directement sur le lien DFS (traduction de \\FileServer03.dundermifflin.inc\Shares vers \\dundermifflin.inc\shares). Ainsi, dès l'ouverture de l'explorateur, Windows lit ces ACL et n'affiche pas le répertoire puisqu'il sait que l'utilisateur n'y a pas accès.

Ce script Powershell permet d'afficher les répertoires DFS qui sont configurés avec la première option et qui apparaissent dans l'explorateur, si jamais vous souhaitez faire une passe complète sur tous vos shares DFS afin d'adopter une configuration homogène :

Param([Parameter(Mandatory=$true)][string] $Namespace)
$fNameSpace = "$Namespace\*"
Get-DfsnFolder $fNameSpace | where-object { (Get-DfsnAccess $_.Path) -eq $null }

Il suffit de l'appeler de cette manière :

.\DFS-NoPerm.ps1 -Namespace \\dundermifflin.inc\shares

Powershell : liste des répertoires DFS portés par un serveur

Travaillant actuellement sur la migration de serveurs de fichiers, j'ai conçu un script Powershell prenant en paramètre le nom d'un serveur afin de lister tous les partages DFS qui y sont hébergés. Le script ne fait pas de distinguo entre les liens DFS actifs et inactifs.

Param([Parameter(Mandatory=$true)][string] $Server)
$roots = Get-DfsnRoot
foreach($root in $roots){
    $path = $root.Path+"\*"
	Get-DfsnFolder $path | Where-Object { (Get-DfsnFolderTarget $_.Path).TargetPath -like "\\$server*" } | select Path -ExpandProperty Path
}

Powershell : automatisation de la création d'un répertoire partagé DFS

J'ai développé un script Powershell permettant d'automatiser tout le process de création d'un répertoire partagé et son ajout à un namespace DFS.

Ce script, prenant en paramètre le nom du partage, va réaliser les opérations suivantes :

  • Création d'un répertoire sur le disque et création d'un partage SMB sur ce même répertoire ;
  • Création et peuplement de groupes AD qui permettront de définir les droits en lecture et écriture ;
  • Ajout des ACL sur le partage ;
  • Ajout du répertoire dans le namespace DFS.

Il sera nécessaire, avant exécution du script, d'éventuellement modifier les variables déclarées en début de code afin de les adapter à l'environnement d'exécution ou au process habituellement suivi.

param([Parameter(Mandatory=$true)][string]$ShareName)
 $path = "D:\DFS"
 $dfspath = "\\dundermifflin.com\DFS"
 $srvname = "PWFILE01"
 $ou = "OU=DFS,OU=Access_Groups,DC=dundermifflin,DC=com"
 $readarray = @()
 $writearray = @()
 $readgroup = "DFS-$ShareName-RX"
 $writegroup = "DFS-$ShareName-W"
 New-Item -path $path -name $ShareName -ItemType "Directory" -ErrorAction Stop | Out-Null 
 New-ADGroup -Name $readgroup -GroupCategory Security -GroupScope DomainLocal -Path $ou -Description "\\dundermifflin.com\DFS\$ShareName - Read Only" -ErrorAction Stop
 New-ADGroup -Name $writegroup -GroupCategory Security -GroupScope DomainLocal -Path $ou -Description "\\dundermifflin.com\DFS\$ShareName - Write" -ErrorAction Stop
 New-SmbShare -Name $ShareName -Path "$path\$ShareName" -FullAccess "BUILTIN\Administrators" -ReadAccess $readgroup -ChangeAccess $writegroup -ErrorAction Stop
 Write-Host "You will be prompted the usernames to add to the read-only access group. Please input only one user at a time. When you are done, leave the field empty and press enter."
 do {
     $readacc = Read-Host "Account name to add to the READ group"
     if($readacc -ne "") {$readarray+=$readacc}
 }
 while($readacc -ne "")
 Write-Host "Now you will be prompted the usernames to add to the modification group. Please input only one user at a time. When you are done, leave the field empty and press enter."
 do {
     $writeacc = Read-Host "Account name to add to the WRITE group"
     if($writeacc -ne "") {$writearray+=$writeacc}
 }
 while($writeacc -ne "")
 if($readarray -ne $null) { Add-ADGroupMember -Identity $readgroup -Members $readarray }
 if($writearray -ne $null) { Add-ADGroupMember -Identity $writegroup -Members $writearray }
 $acl = Get-Acl "$path\$ShareName"
 $acl.SetAccessRuleProtection($true,$false)
 $readacl = New-Object Security.AccessControl.FileSystemAccessRule("DUNDERMIFFLIN\$readgroup","ReadAndExecute, Synchronize",3,0,"Allow")
 $acl.SetAccessRule($readacl)
 $writeacl = New-Object Security.AccessControl.FileSystemAccessRule("DUNDERMIFFLIN\$writegroup","Modify, Synchronize",3,0,"Allow")
 $acl.SetAccessRule($writeacl)
 $sysacl = New-Object Security.AccessControl.FileSystemAccessRule("NT AUTHORITY\System","FullControl",3,0,"Allow")
 $acl.SetAccessRule($sysacl)
 $admacl = New-Object Security.AccessControl.FileSystemAccessRule("BUILTIN\Administrators","FullControl",3,0,"Allow")
 $acl.SetAccessRule($admacl)
 $acl | Set-Acl
 New-DfsnFolder -path "$dfspath\$ShareName" -Target "\\$srvname\$ShareName"

Une version commentée du script est disponible en téléchargement.