Powershell : DFS Operations with TargetPath


This article is about manipulating DFS folder targets paths, which in this particular example is primarily used to distribute user profiles around your company, first you need to make sure your company is using DFS.

DFS ? 

DFS stands for Distributed File System and it does exactly that, you first start with a DFS route that has a registered name in your domain, for example, if you were, the main name is bear.local then the root of your DFS could be:

\\bear.local\Claws

The folder of “claws” would then be your root directory, you can then start building your DFS folder structure underneath that so you could then have a User folder that would have the path of:

\\bear.local\Claws\Users

The DFS folder structure and root is stored on what is known as DFS names space servers, these servers do not need to be domain controllers, but in many configurations, it might be!

Folder Targets

When you create a folder, you have what is known as a folder target, this is where the distributed comes from, you can then point a user profile at different “paths” of data, so if we take our folder structure from above and then add a user onto that folder we get something like this:

\\bear.local\Claws\Users\lee.croucher

Creating Links (or folder targets)

If you just create the folder with no target path, it will simply just act as a folder, However, once we create the username folder, we can then add a link and point it at a file system that offers an SMB share:


Here we can see that folder with a single link to the target:


Multiple folder targets

DFS gives you the other advantage that you can add multiple folder target, so if we go back to that username and add a second link to a different location we will observe two links, not one:


When you at your second link, you will be asked if you would like to replication group, I would recommend you say no to this, Furthermore, I do not recommend you use DFS-R (DFS replication) to replicate data between these folders, I have used this in the past and I find it to be very buggy and inefficient it’s far easier to robocopy data between the two locations.

Mission Control : The Goal

The reason is article is because many of our folders only have one link, and the general consensus with user data, you will have one active share, it’s best not to have multiple shares active because you can end up with data duplication.

If you look at the DFS export (we will get to later on) then you will see this for the user lee.croucher, this shows the "single" with a state value to "2" these are the people we need to find and then apply the new link to those people that only have one active target:

<Link Name="Users\lee.croucher" State="1" Timeout="300" >
<Target Server="smb00" Folder="users$\lee.croucher" State="2" />
</Link>

We then need to move data from one SMB share to the other is therefore requires we need to add an additional target folder as off-line, move the data in the background with another script, then we need to bring the new folder target online and take the old one off-line

The goal here is to complete the following actions in this order, the steps and bold will be the steps we’re going to script:

\\bear.local\Claws\Users\lee.croucher
Current link : \\smbserver\users$\lee.croucher
Copy data with Robocopy
Add new link : \\smb-00\users$\lee.croucher as offline
Enable the new link
Disable the old link

Can I not just use DFS management console?

Yes, you can however, remember that will Be slower than using the script and it will also increase your percentage of human errors and misconfiguration, I would always recommend for consistency with operations like this you stick scripting - you really need a consistent application across all your users.

Export DFS Structure

These commands work on offline DFS data, so to get the file we need to export the DFS structure to a text file you can do this with the command below, replace the DFS root with your actual domain:

dfsutil /root:\\bear.local\slaws /export:exportedroot.txt /verbose

This will place a exportroot.txt file in the current folder which will be used later in the scripts.

Script : DFS-FindValidUsers.ps1

This script will find all the users that only have folder target that is enabled, this works offline as your need to ensure you have exported the DFS structure to a text file from the previous step, the will create a variable called $user that the other script will build on.

# Define the path to your text file
$filePath = "exportedroot.txt"

# Read the content of the file
$fileContent = Get-Content -Path $filePath

# Define a regex to match the target server smbserver and state pattern, capturing the username part
$regex = '<Target Server="smbserver" Folder="users\$\\([^"]+)" State="2" />'

# Process each line to find matches
foreach ($line in $fileContent) {
    if ($line -match $regex) {
        $user = $matches[1]
        Write-Output $user
    }
}

Script : Create-BaseFolderforUsers.ps1

This script will create a new root folder for the user data to be copied to, this will create a empty folder on the target SMB server:

# Define the path to your text file
$filePath = "exportedroot.txt"

# Define the base path where folders will be created
$basePath = "\\smb-00\users$"

# Define a regex to match the target server smb-00 and state pattern, capturing the username part
$regex = '<Target Server="smb-00" Folder="users\$\\([^"]+)" State="2" />'

# Array to hold the extracted usernames
$usernames = @()

# Read the content of the file and extract usernames
$fileContent = Get-Content -Path $filePath
foreach ($line in $fileContent) {
    if ($line -match $regex) {
        $user = $matches[1]
        $usernames += $user
    }
}

# Define a function to sanitize folder names
function Sanitize-Name {
    param (
        [string]$name
    )
    # Remove any illegal characters for Windows folder names
    $illegalChars = [System.IO.Path]::GetInvalidFileNameChars()
    $sanitized = -join ($name.ToCharArray() | Where-Object { $illegalChars -notcontains $_ })
    return $sanitized
}

# Create folders based on the usernames
foreach ($username in $usernames) {
    # Sanitize the username
    $sanitizedUsername = Sanitize-Name -name $username
    
    # Construct the full path for the new folder
    $folderPath = Join-Path -Path $basePath -ChildPath $sanitizedUsername

    # Check if the folder exists
    if (Test-Path -Path $folderPath) {
        Write-Output "Folder already exists: $folderPath"
    } else {
        # Create the folder
        New-Item -Path $folderPath -ItemType Directory -Force
        Write-Output "Created folder: $folderPath"
    }
}

Script : CopyData-Robocopy.ps1

This will copy the data from the source to the target using Robocopy for all users with a single target on the old share:

# Define the path to your text file containing the usernames
$filePath = "exportedroot.txt"

# Define the source and target servers
$sourceServer = '\\smbserver\users$'
$targetServer = '\\smb-00\users$'

# Define a regex to match the target server smbserver and state pattern, capturing the username part
$regex = '<Target Server="smbserver" Folder="users\$\\([^"]+)" State="2" />'

# Array to hold the extracted usernames
$usernames = @()

# Read the content of the file and extract usernames
$fileContent = Get-Content -Path $filePath
foreach ($line in $fileContent) {
    if ($line -match $regex) {
        $user = $matches[1]
        $usernames += $user
    }
}

# Create a function to sanitize folder names
function Sanitize-Name {
    param (
        [string]$name
    )
    # Remove any illegal characters for Windows folder names
    $illegalChars = [System.IO.Path]::GetInvalidFileNameChars()
    $sanitized = -join ($name.ToCharArray() | Where-Object { $illegalChars -notcontains $_ })
    return $sanitized
}

# Perform robocopy for each username without confirmation
foreach ($username in $usernames) {
    # Sanitize the username
    $sanitizedUsername = Sanitize-Name -name $username
    
    # Construct the source and target paths
    $sourcePath = Join-Path -Path $sourceServer -ChildPath $sanitizedUsername
    $targetPath = Join-Path -Path $targetServer -ChildPath $sanitizedUsername

    # Run the robocopy command to copy the data
    robocopy $sourcePath $targetPath /E /Z /COPY:DATS /R:0 /W:0 /IPG:1

    # Check the exit code to determine if the operation was successful or if there were errors
    if ($LASTEXITCODE -le 3) {
        Write-Output "Successfully copied data for user $sanitizedUsername."
    } else {
        Write-Output "Error copying data for user $sanitizedUsername. Robocopy exit code: $LASTEXITCODE"
    }
}

Script : DFS-Operations.ps1 (for all valid users in users$)

This is the final script that will allow you to perform the various operations which will apply to all the users in the $user variable.



# Define the path to your text file containing the usernames
$filePath = "exportedroot.txt"

# Define the DFS paths
$dfsBasePath = '\\bear.local\Claws\Users'
$targetBasePath = '\\smb-00\Users$\'
$oldTargetBasePath = '\\smbserver\users$\'

# Define a regex to match the target server smbserver and state pattern, capturing the username part
$regex = '<Target Server="smbserver" Folder="users\$\\([^"]+)" State="2" />'

# Array to hold the extracted usernames
$usernames = @()

# Read the content of the file and extract usernames
$fileContent = Get-Content -Path $filePath
foreach ($line in $fileContent) {
    if ($line -match $regex) {
        $user = $matches[1]
        $usernames += $user
    }
}

# Function to display menu and execute commands
function Show-Menu {
    param (
        [string[]]$users
    )

    while ($true) {
        # Display menu options
        Write-Output "Select an option:"
        Write-Output "1: Create folder target on SMB-00"
        Write-Output "2: Enable folder target on SMB-00"
        Write-Output "3: Disable folder target on SMBSERVER"
        Write-Output "4: Exit"
        
        # Get user choice
        $choice = Read-Host "Enter your choice (1-4)"

        switch ($choice) {
            1 {
                # Create Offline DFS Link
                foreach ($user in $users) {
                    $path = Join-Path -Path $dfsBasePath -ChildPath $user
                    $targetPath = Join-Path -Path $targetBasePath -ChildPath $user
                    New-DfsnFolderTarget -Path $path -TargetPath $targetPath -State Offline
                    Write-Output "Created Offline DFS Link for user $user"
                }
            }
            2 {
                # Bring Online DFS Link
                foreach ($user in $users) {
                    $path = Join-Path -Path $dfsBasePath -ChildPath $user
                    $targetPath = Join-Path -Path $targetBasePath -ChildPath $user
                    Set-DfsnFolderTarget -Path $path -TargetPath $targetPath -State Online
                    Write-Output "Brought Online DFS Link for user $user"
                }
            }
            3 {
                # Disable Old Location
                foreach ($user in $users) {
                    $path = Join-Path -Path $dfsBasePath -ChildPath $user
                    $oldTargetPath = Join-Path -Path $oldTargetBasePath -ChildPath $user
                    Set-DfsnFolderTarget -Path $path -TargetPath $oldTargetPath -State Offline
                    Write-Output "Disabled old DFS Link for user $user"
                }
            }
            4 {
                # Exit
                Write-Output "Exiting script."
                break
            }
            default {
                Write-Output "Invalid choice. Please select a valid option."
            }
        }
    }
}

# Show the menu and handle user choices
Show-Menu -users $usernames
Previous Post Next Post

نموذج الاتصال