Wednesday, August 30, 2017

Manage DFS from WFA (W2012+)

I already created a post for managing DFS, but that was for W2008 servers, where it still has to be done with dfsutil.exe.
Since 2012 there is better way.
DFS or Distributed File System, is often used in enterprise environments to virtualize your CIFS tree structure.  If you are automating storage, and if so, CIFS, you might also take it a little further and update your DFS.  This powershell code allows you to add/create, remove & offline/online your dfs links and target shares and get the targets of dfs link.

This code uses PS remoting.  Advantage is that the WFA server can run with the local system account and does not need any rights.  The DFS server does need to allow PS remoting, otherwise automation makes no sense.

So the input :
- computername : dfs server
- path : relative or absolute path of the dfs you want to manage
- target : the cifs share
- state : online/offline
- priority : high or low
- credentialname : which wfa credential to use for connection (ps remote)
- domainname : to prepend the username if needed
- action : create/modify/remove


param(
 
    [Parameter(mandatory=$true,helpmessage="The computer name (default localhost)")]
    [string]$ComputerName,
 
    [Parameter(mandatory=$true,helpmessage="The dfs path.  Absolute or Relative")]
    [string]$Path,

    [Parameter(mandatory=$false,helpmessage="The target path (cifs share)")]
    [string]$Target,

    [Parameter(mandatory=$false,helpmessage="The status of the target")]
    [ValidateSet("Offline","Online")]
    [string]$State="Online",

    [Parameter(mandatory=$false,helpmessage="The priority of the target")]
    [validateset("GlobalHigh","GlobalLow")]
    [string]$Priority="GlobalHigh",
 
    [Parameter(mandatory=$true,helpmessage="The name of credentials to find in WFA to connect to remote computer")]
    [string]$CredentialName,

    [Parameter(mandatory=$true,helpmessage="The name of windows domainname")]
    [string]$DomainName,
 
    [Parameter(mandatory=$true,helpmessage="What to do : stop,start or restart")]
    [ValidateSet("Create","Remove","Modify")]
    [string]$Action
 
)

$mycreds = Get-WfaCredentials $CredentialName

# add domainname to username if needed, as it is needed for remote PS !
if(-not $mycreds.UserName.StartsWith($DomainName)){
    $mycreds = New-Object System.Management.Automation.PSCredential ("$DomainName\$($mycreds.UserName)",$mycreds.Password)
}
 
$ErrorActionPreference = "stop"
$workingremote = $false
$session = $null
$realAction = ""
 
function getDfsnRoot($session){
    try{
        Invoke-Command -Session $session -ScriptBlock {Get-DfsnRoot}
    }catch{
        Get-WFALogger -Warn -Message "No dfs root found"
        return $null
    }
}

function getDfsnFolder($path,$session){
    try{
        Invoke-Command -Session $session -ScriptBlock {param($path)Get-DfsnFolder -path $path} -ArgumentList $path
    }catch{
        Get-WFALogger -Warn -Message "No such dfs folder [$path]"
        return $null
    }
}

function getDfsnFolderTarget($path,$target,$session){
    if($Target){
        try{
            Invoke-Command -Session $session -ScriptBlock {param($path,$target,$priority)get-DfsnFolderTarget -path $path -targetpath $target} -ArgumentList $path,$target
        }catch{
            Get-WFALogger -Warn -Message "No such dfs target [$path][$target]"
            return $null
        }
    }else{
        return $null
    }
}

function removeDfsnFolder($path,$session){
    try{
        Invoke-Command -Session $session -ScriptBlock {param($path)remove-DfsnFolder -path $path -confirm:$false} -ArgumentList $path
    }catch{
        Get-WFALogger -Warn -Message "No such dfs folder [$path]"
        return $null
    }
}

function removeDfsnFolderTarget($path,$target,$session){
    try{
        Invoke-Command -Session $session -ScriptBlock {param($path,$target,$priority)remove-DfsnFolderTarget -path $path -targetpath $target -confirm:$false} -ArgumentList $path,$target
    }catch{
        Get-WFALogger -Warn -Message "No such dfs target [$path][$target]"
        return $null
    }
}

function newDfsnFolder{
    param(
        $path,
        $target,
        [ValidateSet("Offline","Online")]
        $state="Online",
        [validateset("GlobalHigh","GlobalLow")]
        $priority,
        $session
    )
    try{
        Invoke-Command -Session $session -ScriptBlock {param($path,$target,$state,$priority)new-DfsnFolder -path $path -targetpath $target -state $state -ReferralPriorityClass $priority -EnableTargetFailback $true} -ArgumentList $path,$target,$state,$priority
    }catch{
        Get-WFALogger -Warn -Message "Cannot create dfs target [$path][$target]"
    }
}

function newDfsnFolderTarget{
    param(
        $path,
        $target,
        [ValidateSet("Offline","Online")]
        $state="Online",
        [validateset("GlobalHigh","GlobalLow")]
        $priority,
        $session
    )
    try{
        Invoke-Command -Session $session -ScriptBlock {param($path,$target,$state,$priority)new-DfsnFolderTarget -path $path -targetpath $target -state $state -ReferralPriorityClass $priority} -ArgumentList $path,$target,$state,$priority
    }catch{
        Get-WFALogger -Warn -Message "Cannot create dfs target [$path][$target]"
    }
}

function setDfsnFolder{
    param(
        $path,
        $target,
        [ValidateSet("Offline","Online")]
        $state,
        [validateset("GlobalHigh","GlobalLow")]
        $priority,
        $session
    )
    Invoke-Command -Session $session -ScriptBlock {param($path,$target,$state,$priority)set-DfsnFolder -path $path -targetpath $target -state $state -ReferralPriorityClass $priority -EnableTargetFailback $true} -ArgumentList $path,$target,$state,$priority
}

function setDfsnFolderTarget{
    param(
        $path,
        $target,
        [ValidateSet("Offline","Online")]
        $state,
        [validateset("GlobalHigh","GlobalLow")]
        $priority,
        $session
    )
    Invoke-Command -Session $session -ScriptBlock {param($path,$target,$state,$priority)set-DfsnFolderTarget -path $path -targetpath $target -ReferralPriorityClass $priority -EnableTargetFailback $true} -ArgumentList $path,$target,$state,$priority
}

try{
    if($Action -eq "Create"){
        if(-not $Target){
            Throw "Target is a mandatory parameter with action `"create`""
        }
        $realAction = $Action
    }
    if($Action -eq "Remove"){
        if($Target){
            $realAction = "RemoveTarget"
        }else{
            $realAction = "RemoveFolder"
        }
    }
    if($Action -eq "Modify"){
        if($Target){
            $realAction = "ModifyTarget"
        }else{
            $realAction = "ModifyFolder"
        }
    }

    Get-WfaLogger -info -message "Connecting to remote computer [$ComputerName]"
    $session = New-PSSession -ComputerName $ComputerName -Credential $mycreds
    $workingremote = $true
    Get-WfaLogger -info -message "Working remote on [$ComputerName]"
 
    if(-not $Path.StartsWith("\\")){
        $Path = $Path.TrimStart("\")
        Get-WfaLogger -info -message "Relative path [$Path]"
        Get-WfaLogger -info -message "Getting root..."
        $root = getDfsnRoot -session $session
        if($root){
            $Path = $root.Path + "\" + $Path
            Get-WfaLogger -info -message "Adding root [$Path]"
        }else{
            Throw "Dfs root not found"
        }
    }

    $dfsfolder = getDfsnFolder -session $session -path $Path
    $dfstarget = getDfsnFolderTarget -session $session -path $Path -target $Target
    
    switch ($realAction){
        "Create" {
            if($dfstarget){
                Get-WfaLogger -info -warn "Target already exists [$Target]"
            }else{
                if($dfsfolder){
                    Get-WfaLogger -info -message "Folder already exists [$Path]"
                    Get-WfaLogger -info -message "Adding new dfs target [$Target]"
                    newDfsnFolderTarget -session $session -path $Path -target $Target -priority $Priority -state $State
                }else{
                    Get-WfaLogger -info -message "Adding new dfs folder [$Path] with target [$Target]"
                    newDfsnFolder -session $session -path $Path -target $Target -priority $Priority
                }
            }
        }
        "RemoveTarget" {
            if($dfstarget){
                Get-WfaLogger -info -message "Removing dfs target [$Target]"
                removeDfsnFolderTarget -session $session -path $Path -target $Target
            }else{
                Get-WfaLogger -info -warn "Target does not exist [$Target]"
            }
        }
        "RemoveFolder" {
            if($dfsfolder){
                Get-WfaLogger -info -message "Removing dfs folder [$Path]"
                removeDfsnFolder -session $session -path $Path
            }else{
                Get-WfaLogger -info -warn "Folder does not exist [$Path]"
            }
        }
        "ModifyTarget" {
            if($dfstarget){
                Get-WfaLogger -info -message "Setting dfs target [$Target][$Priority][$State]"
                setDfsnFolderTarget -session $session -path $Path -target $Target -priority $Priority -state $State
            }else{
                Get-WfaLogger -info -warn "Target does not exist [$Target]"
            }
        }
        "ModifyFolder" {
            if($dfsfolder){
                Get-WfaLogger -info -message "Setting dfs folder [$Path][$State]"
                setDfsnFolder -session $session -path $Path -state $State
            }else{
                Get-WfaLogger -info -warn "Folder does not exist [$Path]"
            }
        }
    }
 
 
}catch{
    throw $_.Exception
}finally{
    if($workingremote){
        $out = Disconnect-PSSession -Session $session -OutVariable dummy
        $out = Remove-PSSession -Session $session -OutVariable dummy
    }
}

2 comments :

  1. Wouldn't it be better just to run the WFA service with a service account that has access to modify and create DFS targets?

    ReplyDelete
    Replies
    1. That works, and if DFS is your only goal, you can go for it. But there are some remarks. This method will require you to install the DFS roles to have the DFS powershell cmdlets. And what if you want to connect to exchange, sql, AD, ... You would need to use that same service account for all kinds of connections. in the end you would have a service account that would have way to many rights, and a whole bunch of management tools installed. By using remote powershell you don't need to install features and roles and tools on the WFA server, and you can choose your credentials for each connection (to dfs, to ad, to exchange, to sql, sharepoint, ...)

      Delete