# encoding: ascii
# api: powershell
# title: Set-DfsnForDR
# description: See www.cosonok.com and Set-DfsnForDR.ps1.
# version: 5.1
# type: function
# author: vCosonok
# license: CC0
# function: Pad-R
# x-poshcode-id: 5495
# x-archived: 2014-10-11T09:30:14
# x-published: 2014-10-09T09:37:00
#
#
#########################################################
## Set-DfsnForDR - v5.1 (September 2014) ##
## =================================================== ##
## CAVEAT UTILITOR! ##
## This program comes with no warranty and no support! ##
#########################################################
$title = "Set-DfsnForDR - v5.1 (September 2014)" # TITLE
$workingPath = $env:USERPROFILE # The users profile (where we expect the optional Set-DfsnForDR.ini to be)
$domainServersFile = $workingPath + "\Set-DfsnForDR.ini" # This is the default file name and location for the DomainServersFile
$backupPath = $workingPath + "\DFSN_BACKUP" # The default backup folder (where namespace backups go)
#############################
# Generic Display Functions #
#############################
Function Pad-R{ # Function Pad-R
Param([string]$string,[int]$int) # Takes in a -string $string, and -int $int
$string.PadRight($int," ").SubString(0,$int) # Pads it right to $padR with " " and cuts it down to size if it was too big to start with
} # END of function Pad-R
Function Columnize{ # Function Columnize
$i = 0 # Initialize counter $i = 0
while($i -lt $args.count){ # While $i is less than the count of arguments
Write-Host ((Pad-R $args[$i] $args[$i+1]) + " ") -F $args[$i+2] -NoNewline # Write to screen $args[i] adjusted to size $args[i], " ", and in color $args[$i+2], with no new line after
$i += 3 # Accumulate count by 3 (since have sets of three arguments)
} # END of while $i < count of arguments
Write-Host # Starts a new line for the next row
} # END of Columnize
Function Wr-E{Write-Host} # Writes a Carriage Return (Enter)
Function Wr-C{Write-Host ($args[0]) -F Cyan} # Writes supplied argument text in Cyan
Function Wr-G{Write-Host ($args[0]) -F Green} # Writes supplied argument text in Green
Function Wr-R{Write-Host ($args[0]) -F Red} # Writes supplied argument text in Red
Function Wr-W{Write-Host ($args[0]) -F White} # Writes supplied argument text in White
Function Wr-Y{Write-Host ($args[0]) -F Yellow} # Writes supplied argument text in Yellow
Function Wn-C{Write-Host ($args[0]) -F Cyan -NoNewline} # Writes supplied argument text in Cyan (no new line after)
Function Wn-G{Write-Host ($args[0]) -F Green -NoNewline} # Writes supplied argument text in Green (no new line after)
Function Wn-R{Write-Host ($args[0]) -F Red -NoNewline} # Writes supplied argument text in Red (no new line after)
Function Wn-W{Write-Host ($args[0]) -F White -NoNewline} # Writes supplied argument text in White (no new line after)
Function Wn-Y{Write-Host ($args[0]) -F Yellow -NoNewline} # Writes supplied argument text in Yellow (no new line after)
Function Rd-W{ # Read from host in White (because default isn't)
If($args){Write-Host ($args[0]) -F White -NoNewline} # If supplied $args then write to the screen $args[0] first with NoNewLine
return (Read-Host "?")} # Then prompt!
Function Prompt-Keys{ # ------------------------------------------------------- # This expects key presses so the args should all be only one character long!
Param([switch]$anykey,[switch]$echo) # Allows any key to be pressed / Allows the key to be echoed to screen
If(!$anykey -and !$args[0]){return $false} # No args when not using the -anykey is not a valid option
If($args[0]){ # Might have no $args[0] if using the -anykey option
If($args[0].count -ne 1){$answers = $args[0]} # If the 1st argument - $args[0] - is an array, use this for the list of possible answers
else{$answers = $args[0..(($args.count)-1)]} # Otherwise, take all the other arguments for the list of possible answers
} # END of If ($args[0]
while($true){ # Infinite loop! Escape via valid keypress or anykey if -anykey set!
If ($echo){$keyPress = $host.UI.RawUI.ReadKey("IncludeKeyDown")} # The Key Press (ECHO allowed)
else {$keyPress = $host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")} # The Key Press (no ECHO)
If($anyKey -and !$args[0]){return $true} # If $anyKey and no $args[0] we an return now
$keyPress = $keyPress.character.ToString().ToUpper() # Compare in upper case (and return in upper case)
foreach($answer in $answers){ # Loop through the answers
if($keyPress -eq $answer.ToUpper()){return $keyPress} # Check if the answer to the prompt equals a valid answer, and if so then return
} # END of foreach $answers
If($anyKey){return $true} # If -anykey don't loop
} # END of while $true
} # --------------------------------------------------------------------------- # END of Prompt-Keys
Function Set-Window{ # ---------------------------------------------------------- # Set-Window
Param([string]$textcolor,[string]$background,[string]$title,[int]$percentMax) # -textcolor COLOR -background COLOR -title TITLE -percentMax %MAX_of_SCREEN_SIZE
$window = (get-host).UI.RawUI # Gets the current Window properties
If($textcolor) {$window.ForegroundColor = $textcolor} # If $textcolor parameter, sets the textcolor
If($background -and ($window.BackgroundColor -ne $background)){ # If $background parameters and it's not equal to what it is set to already
$window.BackgroundColor = $background;cls} # ... set the background and do a CLS
If($title) {$window.WindowTitle = $title} # Set's the Window Title!
$buffer = $window.BufferSize # Gets buffersize
$buffer.Height = 9999 # ... set maximum buffer hit
$window.BufferSize = $buffer # ... apply
If($perCentMax){ # If $perCentMax parameter
$maxX = [int](($window.MaxPhysicalWindowSize.Width)*$percentMax/100) # ... find our new X size
$maxY = [int](($window.MaxPhysicalWindowSize.Height)*$percentMax/100) # ... find our new Y size
$buffer.Width = $maxX # ... buffer = maxX
$window.BufferSize = $buffer # ... apply
$size = $window.WindowSize # ... get WindowSize
$size.Width = $maxX # ... set maxX
$size.Height = $maxY # ... set maxY
$window.WindowSize = $size # ... apply
} # END of $perCentMax
} # ----------------------------------------------------------------------------- # END of Set-Window
##################
# CORE FUNCTIONS #
##################
Function Check-OSVersion{ # ----------------------------- # Checks the OS Version of the machine the script is running on
Param([int]$major,[int]$minor) # INPUT = -major MAJORVERSION -minor MINORVERSION (e.g 2008R2 is 6 1)
$currentOS = [System.Environment]::OSVersion.Version # Get the current OSVersion
if ($currentOS.major -lt $major){return $null} # Current major version less than required major version - return FALSE
elseif ($currentOS.major -gt $major){return $true} # Current major version greater than required major version - return TRUE
elseif ($currentOS.minor -lt $minor){return $null} # Current minor version less than required minor version - return FALSE
else {return $true}} # Current minor version greater than or equal to the minor - return TRUE
Function Got-DFSUtil{ # ------------ # Tests if have access to DFSUtil
Wr-E; $testDfsUtil = dfsutil.exe # This will output a big red error to screen if it fails and leave $testDfsUtil as $null
return $testDfsUtil} # Return $testDfsUtil (either it's $null or has something in)
Function Create-Folder { # ------------------------------------------------------- # INPUT = The folder or path to a folder for a new folder
If(!$args[0]){ return $null } # If no/null argument supplied, return NULL
If(Test-Path $args[0] -ErrorAction SilentlyContinue){ return ($args[0]) } # If it already exists, it returns back the name of the folder
Else {[Void](New-item $args[0] -type directory -ErrorAction SilentlyContinue)} # Otherwise try and create it
If(Test-Path $args[0] -ErrorAction SilentlyContinue){ return ($args[0]) } # If folder now exists, return back the name of the folder
Else { return $null } # Otherwise return NULL
} # ------------------------------------------------------------------------------ # END Create-Folder
Function Prompt-ForDomainServersFile{ # Takes no input
Wr-W "This program allows loading of a file with:" # Display to screen
Wr-W "Line 1 = A domain name for Domain-based Namespaces" # Display to screen
Wr-W "Line 2 = A comma separated list of servers for Standalone Namespaces" # Display to screen
Wr-W "The file contents are not checked for validity.";Wr-E # Display to screen
$readIn = Rd-W "Enter filepath";Wr-E # Read input
return $readIn # Return what was read
} # END of Prompt-ForDomainServersFile
Function Prompt-ForDomain{ # Function takes no input
$readIn = Rd-W "Enter domain name";Wr-E # Read in the domain name
$getDFSUtil = Get-DFSUtil -Domain $readIn # Run Get-DFSUtil -Domain on the domain
If(!$getDFSUtil){ # If was unsuccessful
Wr-R "Unsuccessful querying domain $readIn for namespaces.";Wr-E # ... says it was unsuccessful
return $false # return FALSE
} #
Wr-W ">>>>>>> List of Namespaces in $readIn";Wr-E # Display the list of Namespaces
$getDFSUtil | foreach {Wr-W $_};Wr-E # Cycles through the Get-DFSUtil output
return $readIn # return the domain name given in the prompt
} # END of Prompt-ForDomain
Function Prompt-ForServer{ # Function takes no input
$readIn = Rd-W "Enter server name (or servers separated by commas)";Wr-E # Read in the server name(s)
$split = $readIn.Split(",") # We allow comma separated servers, so split by this
foreach ($server in $split) { # Cycle through the comma seperated list (or just a list of one)
$getDFSUtil = Get-DFSUtil -Server $server # Run Get-DFSUtil -Server on the server
If(!$getDFSUtil){ # If was unsuccessful
Wr-R "Unsuccessful querying server $server for namespaces.";Wr-E # ... say it was unsuccessful
return $false # return FALSE
} #
Wr-W ">>>>>>> List of Namespaces on $server";Wr-E # Display the list of Namespaces
$getDFSUtil | foreach {Wr-W $_};Wr-E # Cycles through the Get-DFSUtil output
} # END of cycling through servers
return $readIn # return the server name(s) given in the prompt
} # END of Prompt-ForServer
Function Display-Namespaces{ # -------------------------- # Takes domain and/or server(s) as input
Param($domain,$server) # Define the parameters
$listNamespaces = @() # Define an array for the list of namespaces
If($domain){ # If supplied a domain name
Wr-W ">>>>>>> List of Namespaces in $domain";Wr-E # A heading
$list = Get-DFSUtil -Domain $domain # Get the list of name spaces in the domain
foreach($item in $list){ # FOR $list
$namespace = "\\" + $domain + "\" + $item # Format Namespaces for domain
$listNamespaces += $namespace # Add to $listNamespaces
Wr-W $namespace # Display the namespaces
} # END $list
Wr-E #
} # END of if $domain
If($server){ # If supplied server(s)
$split = $server.Split(",") # We allow comma separated servers, so split by this
$split | foreach { # For each item (or just one)
Wr-W ">>>>>>> List of Namespaces on $_";Wr-E # A heading
$list = Get-DFSUtil -Server $_ # Get the list of name spaces in the standalone namespace
foreach($item in $list){ # FOR $list
$namespace = "\" + $item # Standalone namespaces just need a "\" on the front of "\COMPUTER"
$listNamespaces += $namespace # Add to $listNamespaces
Wr-W $namespace # Display the namespaces
} # END $list
Wr-E #
} # END of $split for loop
} # END of if $server
, $listNamespaces # Return the list of Namespaces
} # ----------------------------------------------------- # END of Display-Namespaces
Function Get-DFSUtil { # Expected parameters: -Domain OR -Server
Param([string]$Domain,[string]$Server) # Define parameters -Domain and -Server
If($Domain){$dfsutilOut = dfsutil domain $Domain} # Run> dfsutil domain DOMAINNAME
If($Server){$dfsutilOut = dfsutil server $Server} # Run> dfsutil server SERVERNAME
If(!$dfsutilOut){return $false} # If no results, return FALSE
If($dfsutilOut[0].contains("Could not complete the command")){ # dfsutil failed
return $false # return FALSE
} # END of if "Could not complete"
$dfsNameSpaces = @() # Initialize the array $dfsNameSpaces
Foreach ($line in $dfsutilOut) { # Cycle through the lines in the supplied (as 1st argument) array
$line = $line.Trim() # Trim spaces from ends of line!
$tmp = $line.ToLower() # $tmp (TeMPorary) is $line in lowercase (makes matching easier)
If(($tmp -eq "") -or # not interested in blank lines
($tmp.StartsWith("roots on")) -or # not interested in this line
($tmp.StartsWith("done with")) -or # not interested in this line
($tmp.StartsWith("done processing"))){} # not interested in this line {do nothing...}
else {$dfsNameSpaces += $line} # Construct $dfsNameSpaces
} # END of Foreach in $args[0]
, $dfsNameSpaces # Return $dfsNameSpaces
} # END of Function Get-DFSUtil
Function Prompt-ForNamespace { # ----------------------------------------- # Input is the list of namespaces from MAIN ($gotNameSpaces)
Param($namespaces) # Define parameter $namespaces
Wr-W ">>>>>>> List of Namespaces";Wr-E # A heading
$namespaces | foreach {Wr-W $_};Wr-E # Write to screen all the namespaces
$choice = Rd-W "Enter namespace";Wr-E # Prompt for the namespace
If(!$choice){return "ALL"} # If user presses enter, return ALL
## Note: We cannot use -match for the matching because regex is confused by \ ##
foreach ($namespace in $namespaces) { # START: Cycle through $namespaces
If ( $namespace.ToUpper() -eq $choice.ToUpper() ) {return $choice} # If a match of the $choice in the $namespaces, return $choice ...
} # END : Cycle through $namespaces
Wr-R "Unable to match $choice from the list of namespaces!" # ... no match and say so
Wr-E; return "ALL" # ... and return "ALL"
} # ---------------------------------------------------------------------- # END Prompt-ForNamespace
Function Backup-Namespaces { # --------------------------- # Backs up namespaces and returns an array with the filename(s) in
Param($domain,$server,$namespace,$path) # Parameters -domain and/or -server, with -path
Wn-W "Namespaces being backed up to "; Wr-C $path;Wr-E # Display
$filenames = @() # Initialize $filenames array
$date = Get-Date -uformat "%Y%m%d%H%M" # Get the date
If($namespace -ne "ALL"){ # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # If have the $namespace parameter (but not for ALL)
$split = $namespace.Split("\") # We have \\SERVER or DOMAIN\NAMESPACE - [0] \ [1] \ [2] \ [3]
$filename = $path + "\" + $split[2] + "." + $split[3] + "." + $date + ".dfsnbackup" # Create filename
[Void](Get-DFSUtilRootExport $namespace $filename) # Run Get-DFSUtilRootExport
Wn-G "$namespace"; Wn-W " backed up to "; Wr-C "$filename"; Wr-E # write to screen
return $filename # Return just one filename (since chosen to do 1 specific namespace)
} # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # END of $namespace (NOTE: $namespace is overwritten later in this FN)
If($domain){ # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # If DOMAIN
$list = Get-DFSUtil -Domain $domain # Get the list of namespaces
foreach($item in $list){ # cycle the list
$filename = $path + "\" + $domain + "." + $item + "." + $date + ".dfsnbackup" # create filename
$namespace = "\\" + $domain + "\" + $item # format namespace for dfsutil
[Void](Get-DFSUtilRootExport $namespace $filename) # Run Get-DFSUtilRootExport
$filenames += $filename # accumulate filenames
Wn-G "$namespace"; Wn-W " backed up to "; Wr-C "$filename" # write to screen
} # END of $list
} # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # END of if DOMAIN
If($server){ # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # If SERVER(s)
$elements = $server.Split(",") # We allow comma separated servers, so split by this
foreach($element in $elements){ # For each item (or just one)
$list = Get-DFSUtil -Server $element # Get the list of namespaces
foreach($item in $list){ # cycle the list
$split = $item.Split("\") # Server items are \SERVER\NAMESPACE so we split them ([0] \ [1] SERVER \ [2] NAMESPACE)
$filename = $path + "\" + $element + "." + $split[2] + "." + $date + ".dfsnbackup" # create filename
$item = "\" + $item # format namespace for dfsutil
[Void](Get-DFSUtilRootExport $item $filename) # Run Get-DFSUtilRootExport
$filenames += $filename # accumulate filenames
Wn-G "$item"; Wn-W " backed up to "; Wr-C "$filename" # write to screen
} # END of $list
} # END of $split for loop
};Wr-E # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # END of SERVER
, $filenames # Return $filenames
} # ------------ # END of Backup-Namespaces
Function Get-DFSUtilRootExport { # Expected parameters: -Namespace AND -Backup
Param([string]$Namespace,[string]$Backup) # Define parameters -Namespace and -Backup
$dfsutilout = dfsutil root export $Namespace $Backup # Get the DFS Namespace information and write to $Backup
$tmp = $dfsutilout[1].ToString().ToLower() # $tmp (TeMPorary) is $dfsutilout in lowercase (makes matching easier)
If($tmp.Contains("done processing this")){return $true} # Means successfully done a dfsutil root export
$false # ... otherwise it failed!
} # END of Function Get0DFSUtilRootExport
Function Display-TableOfLinks{ # ---------------------------------------------------------------------- # START of Display-TableOfLinks
Param($backupFiles) # Define param -backupfiles
If(!$global:tableOfLinks){ # (v5.1) If $global:tableOfLinks is NULL
[Void](Build-ArrayOfLinks $backupFiles) # ... build array of links
$global:ToLcount = $global:tableOfLinks.count} # ... and count rows in the array (global)
$r = 0;$x1 = 45;$x2 = 45;$x3 = 9;$x4 = 15 # Initialize row accumulator r=0 and column widths for the table
Columnize "Namespace Link" $x1 White "Target" $x2 White "State" $x3 White "PriorityClass" $x4 White # Column Headings
Columnize "--------------" $x1 White "------" $x2 White "-----" $x3 White "-------------" $x4 White # ... underlined
while ($r -lt $global:ToLcount){ # While the row count is less than the number of rows (of 4) we have in the array
Columnize ($global:tableOfLinks[$r]) $x1 White ($global:tableOfLinks[$r+1]) $x2 White ($global:tableOfLinks[$r+2]) $x3 White ($global:tableOfLinks[$r+3]) $x4 White
$r += 4 # Accumulate the array by 4 for the next row
} # END of while $r -lt $rows
Wr-E # Write to screen a carriage return
} # --------------------------------------------------------------------------------------------------- # END of FN (AKA - Display-DFSUtilRootExportTable modified)
Function Build-ArrayOfLinks{ # --------------------------------------------------------- # Cycles through all the backup files building the array of links
Param($backupFiles) # Define param -backupfiles
$fileCount = $backupFiles.Count # A count of backup files
$onFileNum = 1 # Initialize "On File Number" = 0
foreach ($file in $backupFiles){ # Cycle through backup files
Wn-W ("Reading backup file " + $onFileNum + "/" + $fileCount + " - ");Wn-C $file # (v5.1) Display progress
[Void](Get-DFSUtilRootExportTable $file) # (v5.1) Get-DFSUtilRootExportTable updates $global:tableOfLinks!
$onFileNum ++ # Accumulate number of file we're on (progress display)
} # END of $backupFiles
Wr-E #
} # ------------------------------------------------------------------------------------ # END of Build-ArrayOfLinks
Function Get-DFSUtilRootExportTable { # ---------------------------------- # Expected parameter: -Filename
# INPUT = $Filename of a DFSUTIL ROOT EXPORT file! #
# OUTPUT = NONE! (Updates $global:tableOfLinks) #
# USED BY = Function Build-ArrayOfLinks #
Param([string]$Filename) # Define parameter -Filename
$tmp = Test-Path $Filename # Test for $filename
If(!$tmp){Wr-R "Filename $Filename not found!";return $null} # Return NULL and display a warning if $filename not found
$contents = Get-Content -Path $Filename # Get the contents of $Filename
If(!$contents){Wr-R "Filename $Filename has no content!";return $null} # Return NULL and display a warning if no content
$recording = $false # Variable turns on/off recording from the file
If (!$global:tableOfLinks){$global:tableOfLinks = @()} # (v5.1) A check that $global:tableOfLinks is not null, and if it is, to initialize as an array
$split = $Filename.split("\") # (v5.1) We record full back to backup file and
$split = $split[($split.count)-1] # (v5.1) ... just want the end bit (the file name)
$split = $split.Split(".") # (v5.1) Split backup file name on "."
$namespacePrefix = "\\" + $split[0] + "\" + $split[1] +"\" # (v5.1) The (DEFAULT) filename is DOMAIN/COMPUTER[0].NAMESPACE[1]
Foreach ($line in $contents){ # Go through all the lines in $contents
Wn-C "." # (v5.1) Display progress
$tmp = $line.ToLower() # $tmp (TeMPorary) is $line in lowercase (makes matching easier)
If($tmp.Contains("</link>")){$recording = $false} # STOP recording contents of the file with "</link>"
If($recording){ # This bit only activates if $recording for servernames
$split = $line.Split([char]34) # Split $line on Quotations
$state = $split[1] # State is [0] " [1] STATE
$priorityClass = $split[3] # Priority Class is [0] " [1] STATE " [2] " [3] PriorityClass
$split = $line.Split(">") # Split $line on >
$split = $split[1] # Take [1] ( [0] > [1] )
$split = $split.Split("<") # Split the above on <
$target = $split[0] # Target is [0] Target < [1]
$global:tableOfLinks += $link # (v5.1) Contruct array: link
$global:tableOfLinks += $target # (v5.1) Contruct array: target
$global:tableOfLinks += $state # (v5.1) Contruct array: state
$global:tableOfLinks += $priorityClass # (v5.1) Contruct array: priorityClass
} # END of $recording
If($tmp.Contains("<link name=")){ # If the line contains "<link name="
$recording = $true # ... start recording from the next line
$link = $line.Split([char]34) # Split the line on "
$link = $link[1] # The link is [0] " [1] LINK "
$link = $namespacePrefix + $link # (v5.1) Formats the link as a full path
If($link.contains(".DFSFolderLink")){$recording = $false} # FIX for condition where a DFS link has no targets and temporary link name="<LINK>\.DFSFolderLink"
} # END of $tmp contains "<link name ="
} # END of foreach $line n $contents
Wr-E # (v5.1) Display progress - carriage return after the progress dots ...
} # ---------------------------------------------------------------------- # END of function Get-DFSUtilRootTable
Function Display-TargetServers{ # ---------------------- # START: Display-TargetServers
# INPUT = NONE but uses $global:tableOfLinks
# OUTPUT = NONE but constructs and/or displays $global:targets (hashtable with all targets in alphabetical order and how many times they've occurred!)
If(!$global:targets){ # If we don't already have the $global:targets hash table
$global:targets = @{} # Initializes $global:targets as a hash table
$i = 0 # Initialize $i = 0 (location in the table of links)
while ($i -lt $global:ToLcount){ # Goes through all the entries in the Table of Links
$target = $global:tableOfLinks[$i+1] # Because $table has 4 entries per row [0] Namespace Link [1] Target ...
$server = $target.Split("\")[2] # The server is ... [0] \ [1] \ [2] SERVERNAME \ [3] ...
$count = $global:targets.Get_Item($server) # Get count of number of links with this server (note: we don't check capitalization of server name)
If(!$count){ # If no count ...
$global:targets.Set_Item($server,1) # ... start with a count of 1
} else { # otherwise
$count++ # accumulate the counter
$global:targets.Set_Item($server,$count) # and set the hashtable for $server with value $count
} # END of If !$count else $count
$i += 4 # Accumulate $i by 4 (represents 4 entries per row in the table)
} # END of while Less Than $sizeOfLinksTable
} # END of If !$global:targets
$str = $global:targets.GetEnumerator() | Sort-Object -Property Name | Format-Table -Autosize | Out-String
$str = $str.Trim() # Trim carriage returns from front and end!
Wr-W $str; Wr-E # Write table to screen
} # ---------------------------------------------------- # END: Display-TargetServers
Function Prompt-ForTarget{ # -------------------------------------------------------------------------- # FN: Prompt-ForTarget
$answer = Rd-W "Enter target FQDN/NETBIOS";Wr-E # The prompt
If(!$answer){return $null} # If no answer return NULL
If(!$global:targets.get_item($answer)){ # If no match
Wr-R "$answer is not a valid target from the List of 'Servers with Targets in the Namespaces'!" # ... say no match
Wr-E; return $null} # And return NULL
return $answer} # --------------------------------------------------------------------------------- # Else return $answer
########################
# Update DFS Functions #
########################
Function Update-DFSUtil{ # ------------------- # Function Update-DFSUtil
# INPUT: $global variables:tableOfLinks,TolCount,first,last,enabled,disabled
Param([switch]$ShowCommands,[switch]$Safe) # Define switches
If(!$ShowCommands){ # Not showing table headers or any headers if -ShowCommands!
$x1 = 45;$x2 = 45;$x3 = 9;$x4 = 15 # Column widths for tables (row output done in Update-DFSUtilProperty)
Columnize "Namespace Link" $x1 White "Target" $x2 White "State" $x3 White "PriorityClass" $x4 White
Columnize "--------------" $x1 White "------" $x2 White "-----" $x3 White "-------------" $x4 White} # END !$showCommands
# \/ This next 4 lines use Update-DFSUtilProperty to do DFSUTIL property updates for different properties \/ #
If($global:first) {[Void](Update-DFSUtilProperty -Match ($global:first) -P1 "priorityclass" -P2 "set" -S1 "GlobalHigh" -Column "PriorityClass" -Display "GlobalHigh")}
If($global:last) {[Void](Update-DFSUtilProperty -Match ($global:last) -P1 "priorityclass" -P2 "set" -S1 "GlobalLow" -Column "PriorityClass" -Display "GlobalLow")}
If($global:enabled) {[Void](Update-DFSUtilProperty -Match ($global:enabled) -P1 "state" -P2 "online" -Column "State" -Display "ONLINE")}
If($global:disabled){[Void](Update-DFSUtilProperty -Match ($global:disabled) -P1 "state" -P2 "offline" -Column "State" -Display "OFFLINE")}
Wr-E # ... blank line before the menu title
} # ------------------------------------------ # Note: (v5.1) Removed $m since we now check the target is valid on input!
Function Update-DFSUtilProperty { # ---------- # This Function is used inside Update-DFSUtil ONLY
Param($Match,$P1,$P2,$S1,$Column,$Display) # Define parameters -Match -P1 -P2 -S1 -Column -Display
$i = 0 # Initialize i as 0 (counts where we are in $arrayOfLinks FROM Update-DFSUtil)
If ($safe -and !$ShowCommands) {Wr-E; Wr-G "Running in safe mode, no updates will be actioned!";Wr-E}
elseif (!$safe -and !$ShowCommands){Wr-E; Wr-C "DFSUtil property Updates being actioned!";Wr-E}
while ($i -lt $global:ToLcount){ # WHILE < count of $global:TableOfLinks
$target = $global:tableOfLinks[$i+1] # Target is taken from $arrayOfLinks
$targetServer = ($target.split("\"))[2] # Because we have \\SERVER\ or [0]\[1]\[2]SERVER\
If( $targetServer.ToLower() -eq $Match.ToLower() ){ # If we have a match (has to be a PERFECT MATCH, letter for letter, but not case sensitive)
$namespaceLink = $global:tableOfLinks[$i] # Get the namespace link from $arrayOfLinks
$state = $global:tableOfLinks[$i+2] # Get original state from $arrayOfLinks
$priorityClass = $global:tableOfLinks[$i+3] # Get original priority class from $arrayOfLinks
If($ShowCommands){ # IF we're just showing the commands ($showCommands from the parent function)
Wr-C "dfsutil property $P1 $P2 $namespaceLink $target $S1" # Display the command that would get run
} else { # ELSE (!$ShowCommands)
If(!$safe){ # @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ # @@ ATTENTION - THE DFS UPDATE BIT @@
$commandOut = dfsutil property $P1 $P2 $namespaceLink $target $S1 # THIS BIT ONLY RUNS IF SAFE MODE IS NOT ENABLED!
} # @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ # @@ END OF DFS UPDATE BIT @@
If($Column -eq "PriorityClass"){Columnize $namespaceLink $x1 White $target $x2 White $state $x3 White $Display $x4 Cyan}
If($Column -eq "State"){Columnize $namespaceLink $x1 White $target $x2 White $Display $x3 Cyan $priorityClass $x4 White}
} # END of not showing commands
} # END of if $Match
$i += 4 # Accumulate $i by 4 ($array of links is a 1xY array - Y is in multiples of 4)
} # END of while < $count
} # ------------------------ # Note: (v5.1) Removed $recordMatches since we now checks the target is valid on input!
##################
## MAIN PROGRAM ##
##################
# Basic Checks.
If($PSIse) {Wr-E;Wr-R "This program cannot run in PowerShell ISE. ISE does not support ReadKey!";Wr-E;EXIT}
If(!(Check-OSVersion 6 0)){Wr-E;Wr-R "This program requires Windows Version 6.0 (Windows 2008) or better!";Wr-E;Prompt-Keys -AnyKey;EXIT}
If(!(Got-DFSUtil)) {Wr-E;Wr-R "This program requires access to DFSUTIL.EXE!";Wr-E;Prompt-Keys -AnyKey;EXIT}
# Note: We do not check AD rights permissions to DFS, but if the account doesn't have enough permission data gathering will fail!
Set-Window White Black $title 80 # Set White text, Black background, Window Title, @ 80% max window size
Wr-E;Wr-W "<<<<<<<<< $title >>>>>>>>>";Wr-E # Display TITLE
$modes = "Set Referral Priority Class (For Clustered ONTAP)","Set State Online/Offline (For Data ONTAP 7-Mode)" # Initialize available modes
$safeModes = "Safe Mode ENABLED (Updates NOT possible)!","Safe Mode DISABLED (UPDATES will be ACTIONED)!" # Initialize description for safe modes
$mode = 0;$safeMode = $true # Initialize mode for CDOT and safe mode TRUE
$nsChoice = "ALL"; $loadFile = $true # Initialize selection of ALL namespaces; enable loading the Domain Servers File on first run
$global:tableOfLinks = $global:ToLcount = $global:targets = $null # Global variables we set to NULL
$global:first = $global:last = $global:enabled = $global:disabled = $null # Global variables we set to NULL
$reset1 = $reset2 = $reset4 = $reset4 = $domainName = $servers = $gotNameSpaces = $domainOrServer = $backup = $null # Non-global ones we set to NULL
$backupPath = Create-Folder $backupPath # This returns back the default $backupPath if it was created or already exists
Wn-W "Checking for the default Domain and Servers file at "; Wr-C $domainServersFile; Wr-E # Display on first running the script
while($true){ # START of 'MAIN MENU SYSTEM' ... a perpetual loop
If($loadFile){ # If $loadFile then load the domainServersFile
If(Test-Path $domainServersFile){ # Check if a $domainServersFile exists
$content = Get-Content $domainServersFile # Get the file contents
If($content[0]){$domainName = $content[0]} # Domain is on the first line (or blank)
If($content[1]){$servers = $content[1]} # Servers (separated by commas) are on the second line (or blank)
} else { # Otherwise, no file
$domainServersFile = $null # Set the string $domainServersFile to NULL
} # NOTE: There is (currently) no error checking in here for the validity of the file contents
$loadFile = $null # Will not reload the file on subsequent starts of the main menu (unless a new file is selected for loading)
} # END: If $loadFile
If ($reset1){$reset1 = $gotNameSpaces = $null; $nsChoice = "ALL"; $reset2 = $true}
If ($reset2){$reset2 = $backup = $null; $reset3 = $true}
If ($reset3){$reset3 = $global:tableOfLinks = $global:targets = $null; $reset4 = $true}
If ($reset4){$reset4 = $global:first = $global:last = $global:enabled = $global:disabled = $null; $safeMode = $true}
## MAIN MENU ##
Wr-W "<<<<<<<<< MAIN MENU >>>>>>>>>";Wr-E # MAIN MENU
$PROMPTKEYS = "X","Q","B","1","2","3" # Initialize the key selections array with always available options
If($domainName -or $servers){$domainOrServer = $true # Set the $domainOrServer flag to TRUE if we have any
} else { $domainOrServer = $null} # ... otherwise set it to NULL
# ~~~~~ DISPLAY: Options B,1 - 3 ~~~~~ #
If($backupPath) { Wn-W " <B> Change backup folder. Current = ";Wr-C $backupPath}
else { Wr-R " <B> Select a backup folder!"}
If($domainServersFile){ Wn-W " <1> Reload Domain and Server(s) from file. Last loaded = ";Wr-C $domainServersFile}
else{ Wr-W " <1> Load Domain and Server(s) from file"}
If($domainName){ Wn-W " <2> Change Domain. Currently selected = ";Wr-G $domainName}
else{ Wr-W " <2> Enter Domain Name (for Domain-based namespaces)"}
If($servers){ Wn-W " <3> Change Server(s). Currently selected = ";Wr-G $servers}
else{ Wr-W " <3> Enter Server Name(s) (for Standalone namespaces)"}
# ~~~~~ DISPLAY: Options 4 - 8 ~~~~~ #
If($domainOrServer){ Wr-E;Wr-W " <4> Display Namespaces";$PROMPTKEYS += "4"}
If($gotNameSpaces){ Wn-W " <5> Select an individual namespace/select ALL. Currently selected = ";Wr-C $nsChoice;$PROMPTKEYS += "5"}
If($gotNameSpaces -and $backupPath){
Wr-W " <6> Backup Namespaces";$PROMPTKEYS += "6"}
If($backup){ Wr-W " <7> Display Table of Links, Target, State and PriorityClass";$PROMPTKEYS += "7"}
If($global:tableOfLinks){ Wr-W " <8> Display List of Servers with Targets in the Namespaces";$PROMPTKEYS += "8"}
# ~~~~~ DISPLAY: Options T,F,L,E,D ~~~~~ #
$common = "Set Server whose Targets are to be set as"
If($global:targets){ Wr-E;Wn-W " <T> (T)oggle Mode of Operation. Current = ";Wr-C $modes[$mode];$PROMPTKEYS += "T"
If($mode -eq 0){ $PROMPTKEYS += "F","L"
If($global:first){ Wn-W " <F> $common Priority (F)irst. "; Wr-G ("Selected = " + $global:first)}
else{ Wr-W " <F> $common Priority (F)irst."}
If($global:last){ Wn-W " <L> $common Priority (L)ast . "; Wr-G ("Selected = " + $global:last)}
else{ Wr-W " <L> $common Priority (L)ast ."}
} else { $PROMPTKEYS += "E","D"
If($global:enabled){ Wn-W " <E> $common (E)nabled . "; Wr-C ("Selected = " + $global:enabled)}
else{ Wr-W " <E> $common (E)nabled ."}
If($global:disabled){Wn-W " <D> $common (D)isabled. "; Wr-C ("Selected = " + $global:disabled)}
else{ Wr-W " <D> $common (D)isabled."}}}
# ~~~~~ DISPLAY: Options C,S,U ~~~~~ #
If($global:last -or $global:first -or $global:enabled -or $global:disabled){
$PROMPTKEYS += "S","C","U"
Wr-E;Wr-C " <C> Display DFSUTIL (C)ommands that will be run."
If($safeMode){ Wn-G " <S> Toggle (S)afe Mode On/Off. Current = "; Wr-G $safeModes[0]
Wr-G " <U> (U)PDATE DFS!"
} else { Wn-Y " <S> Toggle (S)afe Mode On/Off. Current = "; Wr-Y $safeModes[1]
Wr-Y " <U> (U)PDATE DFS!"}}
# ~~~~~ DISPLAY: Exit and Press a Key ~~~~~ #
Wr-E;Wr-W " <X/Q> E(X)it/(Q)uit!"
Wr-E;Wn-G " <<<<< Press a Key: "
## Handle the Keys ##
$key = Prompt-Keys $PROMPTKEYS;Wr-Y $key; Wr-E
# <<<<<<< HANDLE: X,B,1,2,3 >>>>>>> #
If(($key -eq "X") -or ($key -eq "Q")){EXIT}
If($key -eq "B"){$backupPath = Rd-W "Enter backup folder path"; $backupPath = Create-Folder $backupPath; Wr-E; $reset2 = $true}
If($key -eq "1"){$domainServersFile = Prompt-ForDomainServersFile; $loadFile = $true; $reset1 = $true}
If($key -eq "2"){$domainName = Prompt-ForDomain; $reset1 = $true}
If($key -eq "3"){$servers = Prompt-ForServer; $reset1 = $true}
# <<<<<<< HANDLE: 4 - 8 >>>>>>> #
If($key -eq "4"){$gotNameSpaces = Display-Namespaces $domainName $servers} # KEY: "4" get's a properly formatted table of Namespaces
If($key -eq "5"){ # KEY: "5" selection
$newChoice = Prompt-ForNamespace $gotNameSpaces # Prompt for new choice if NameSpace
If($newChoice -ne $nsChoice){$reset2 = $true} # If $newChoice is different to the old, we need to reset $reset2 set of variables
$nsChoice = $newChoice # Always set $nsChoice to $newChoice
} # END: "5" selection
If($key -eq "6"){ # KEY: "6" for backup!
$backup = Backup-Namespaces -Domain $domainName -Server $servers -Namespace $nsChoice -Path $backupPath
$reset3 = $true} # Running a backup always re-initializes the $global:tableOfLinks to a NULL array
If($key -eq "7"){[Void](Display-TableOfLinks $backup)} # KEY: "7" generates and/or displays $global:tableOfLinks
If($key -eq "8"){[Void](Display-TargetServers)} # KEY: "8" generates and/or displays $global:targets (hashtable of target servers & occurrences)
# <<<<<<< HANDLE: T,F,L,E,D >>>>>>> #
If($key -eq "T"){ # KEY: "T" (T)oggle
If($mode -eq 0){$mode = 1} # Mode now "Set State"
else {$mode = 0} # Mode now "Set PriorityClass"
$reset4} # Toggling mode resets these variables
If($key -eq "F"){$global:first = Prompt-ForTarget} # KEY: "F"
If($key -eq "L"){$global:last = Prompt-ForTarget} # KEY: "L"
If($key -eq "E"){$global:enabled = Prompt-ForTarget} # KEY: "E"
If($key -eq "D"){$global:disabled = Prompt-ForTarget} # KEY: "D"
# <<<<<<< HANDLE: S,C,U >>>>>>> #
If($key -eq "S"){ # START: S selection for (S)afe
If($safeMode){$safeMode = $null} # ... not SAFE mode
else {$safeMode = $true}} # ... now SAFE mode
If($key -eq "C"){ # --------------------- # START: C selection for (C)ommands
[Void](Update-DFSUtil -ShowCommands)} # ... RUN but just show commands
If($key -eq "U"){ # --------------------- # START: U selection for (U)pdate
If($safeMode){ # ... If safe mode
[Void](Update-DFSUtil -Safe) # RUN Update-DFSUtil in safe mode
} else { # ... else ...
[Void](Update-DFSUtil) # RUN Update-DFSUtil
$reset2 = $true # ... after FULL run, run $reset2 (will need to backup before more changes!)
} # END: NOT $safeMode
} # ------------------------------------- # END: R selection
} # END of 'MAIN MENU SYSTEM' ... loop back to start
#########################
## END of MAIN PROGRAM ##
#########################