Sunday, 30 April 2017

Automating CIFS Server Domain Joins for ONTAP

There’s probably not much demand for automating CIFS Server Domain joins (cifs server create). Still, if you have to create a lot of SVMs, and each SVM has a CIFS server on the same domain, and you don’t want to keep having to enter the domain administrative credentials (or getting someone else to do this), this blog post might be of interest.

CIFS Server domain join in ClusterShell

Using ClusterShell to do CIFS Server Domain joins is fine - unless you have lots to do. If you have lots to do, then having to keep entering user name and password is a pain. Example below:


C910::> cifs server create -vserver SVM1 -cifs-server SVM1 -domain lab.priv

In order to create an Active Directory machine account for the CIFS server, you must supply the name and password of a Windows account with sufficient privileges to add computers to the "CN=Computers" container within the "LAB.PRIV" domain.

Enter the user name: administrator
Enter the password: ********


CIFS Server domain joins in PowerShell

In PowerShell we can save the domain credentials so we don’t need to keep inputting them. The below is an example of how to do a domain join in PowerShell using the Data ONTAP PowerShell Toolkit.

The penultimate line is where we store the domain credentials. The domain join is in the final line. And the rest is just setup (intentionally verbose to make it clear what we’re doing.) This is not a PowerShell script, this is just typed (or pasted) directly after the PowerShell prompt>


Import-Module DataONTAP

$ClusterCredential = New-Object System.Management.Automation.PsCredential($(Read-Host "Cluster Username"),$(Read-Host "Cluster Password" -AsSecureString))

$CLUSTER = "10.0.1.70"
Add-NcCredential -Name $CLUSTER -Credential $ClusterCredential
Connect-NcController $CLUSTER

$DomainCredential = New-Object System.Management.Automation.PsCredential($(Read-Host "Domain Username"),$(Read-Host "Domain Password" -AsSecureString))

Add-NcCifsServer -VserverContext SVM1 -Name SVM1 -Domain lab.priv -AdminCredential $DomainCredential -Force


Image: Example Output (note the domain username is administrator@lab.priv)

And for subsequent CIFS Server domain joins, you can just copy and paste a script of PowerShell commands into PowerShell>


Add-NcCifsServer -VserverContext SVM2 -Name SVM2 -Domain lab.priv -AdminCredential $DomainCredential -Force
Add-NcCifsServer -VserverContext SVM3 -Name SVM3 -Domain lab.priv -AdminCredential $DomainCredential -Force
Add-NcCifsServer -VserverContext SVM4 -Name SVM4 -Domain lab.priv -AdminCredential $DomainCredential -Force


And on…

BONUS UPDATE: CIFS Server domain re-joins in PowerShell

If you want to rename the CIFS server (re-join AD with a new AD Machine Account), the PowerShell is like below (where SVM1 is the new CIFS server name - might have been SVM1-TEMP before):


set-nccifsserver -VserverContext SVM1 -AdministrativeStatus down -Domain lab.priv -AdminCredential $DomainCredential
set-nccifsserver -VserverContext SVM1 -CifsServer SVM1 -Domain lab.priv -AdminCredential $DomainCredential -Force


Saturday, 29 April 2017

Usermap 7 to C (Usermap7toC.ps1)

At the start of the month I wrote about 7 to C Gran Turismo (7CGT). To neatly close off the month, here’s a tool for translating 7-Mode usermap into (Clustered) ONTAP name-mappings. The Usermap7toC tool creates a script of Clustershell commands.

Like 7CGT, the Usermap7toC tool is designed to handle multiple consolidations of pFilers and vFilers down to a single SVM, and detect conflicts (conflicting name mappings across different filers) where remediation is required.

Like 7CGT, the Usermap7toC tool can be run online (with ONTAPI access to 7-Mode systems and ONTAP systems), or offline (where you have no ONTAPI access, but can be provided with config files.) Here I’ll just cover how to run in online mode.

How to Run Usermap 7 to C

First, you’ll need to prepare the file U7C.7ModeFilers.TXT. Example content:


FAS101,vfiler0
FAS101,FAS101_VF1


Then just run in PowerShell (PS>) as:


.\Usermap7toC.ps1


And follow the prompts.

Note: It does need the following files to exist (online mode will create these files unless there’s no content - i.e. no current ONTAP usermaps - in which case just create blank text files with these names.)
- U7C.CdotNameMap.TXT
- U7C.CdotUnixGroups.TXT
- U7C.CdotUnixUsers.TXT

Image: Usage Example of Usermap 7 to C (up to final prompt)

Any questions, please let me know and I’ll endeavour to reply.

The PowerShell Code

Apologies the code is a bit clunky. It was basically a lift and shift of the usermap part from the 7CGT code, hence there’s more code than necessary.


#########################
## U7C: Usermap 7 to C ##
#########################

Param(
  # 7-Mode pFiler and any vFilers as lines of PFILER,{VFILER or "vFiler0"}
  [String]$7ModeFilersFile = "U7C.7ModeFilers.TXT",
  # CDOT UNIX USERS file/filepath
  [String]$CdotUnixUsersFile = "U7C.CdotUnixUsers.TXT",
  # CDOT UNIX GROUPS file/filepath
  [String]$CdotUnixGroupsFile = "U7C.CdotUnixGroups.TXT",
  # CDOT NAME MAPPING file/filepath
  [String]$CdotNameMappingFile = "U7C.CdotNameMap.TXT",
  # Output File Name
  [String]$OutputFileName = "U7C.Output.TXT"
)
[String]$Version = "1.0"

<#
(Info for OFFLINE mode)
To obtain the CDOT Unix Users, Unix Groups, and Name-Mappings ...
... please run via the Clustershell -
::> row 0; set -showseparator ","
- followed by the command(s) below ...
... and save into separate text files (hash out column headers).
UNIX USERS:
::> unix-user show -vserver $SVM -fields user,id,primary-gid,full-name
UNIX GROUPS:
::> unix-group show -vserver $SVM -fields name,id
USERMAP:
::> name-mapping show -vserver $SVM -fields direction,position,pattern,replacement
#>

## BASIC DISPLAY FUNCTIONS / GENERIC FUNCTIONS ##
## =========================================== ##

FUNCTION Uline{$args[0];("## " + "".PadRight($args[0].length -6,"=") + " ##");""}
FUNCTION Wr{Param([String]$P,[String]$C = "WHITE")
  IF($P){Write-Host $P -ForegroundColor $C -NoNewLine}ELSE{Write-Host}}
FUNCTION PressEnterToContinue{Wr "Press Enter to continue... " Cyan;[Void](Read-Host)}
FUNCTION EXITING{Wr " - exiting!" RED;Wr;Wr;PressEnterToContinue;EXIT}
FUNCTION PromptValidator{
  Param([Switch]$YesOrNo) # $args is used
  WHILE ($TRUE){
    $ReadIn = Read-Host
    IF($YesOrNo){ # CONDITION 1: Yes or No
      IF(($ReadIn -eq "YES") -or ($ReadIn -eq "Y")){RETURN $True }
      IF(($ReadIn -eq "NO")  -or ($ReadIn -eq "N")){RETURN $False}
    } ELSEIF($args[0]){ # CONDITION 2: Check input against arguments
      FOREACH($argument in $args){IF($ReadIn -eq  $argument){RETURN $ReadIn}}
    } ELSE { # CONDITION 3: Make sure input >= 1 char
      IF($ReadIn.length -ge 1){RETURN $ReadIn}}}
}
FUNCTION PromptSetting{
  Param([String]$Prompt,[String]$Setting,[Switch]$AllowEnter)
  Wr $Prompt CYAN
  # If we have SETTING defined, write to host SETTING and return SETTING!
  IF($Setting){Wr $Setting;Wr;RETURN $Setting}
  # If we allow enter (e.g "") to read-host, we can return "" or the new setting
  IF($AllowEnter){$NewSetting = Read-Host;RETURN $NewSetting}
  # Otherwise, don't return until we've got more than a ""
  RETURN (PromptValidator)
}
FUNCTION ProcessYesNoSwitch{
  Param([String]$Question,[Boolean]$EnableSwitch,[Boolean]$DisableSwitch)
  Wr $Question CYAN
  IF($EnableSwitch){Wr "YES";Wr;RETURN $true}
  ELSEIF($DisableSwitch){Wr "NO";Wr;RETURN $false}
  ELSE{PromptValidator -YesOrNo}
}
FUNCTION ProcessArray {
  Param([System.Array]$ArrayToProcess,[Switch]$AllowHash)
  # AllowHash because some files (like CIFS share names) use hashes
  [System.Array]$ProcessedArray = @()
  FOREACH ($ArrayLine in $ArrayToProcess){
    # Remove blank lines ""
    IF($ArrayLine -ne ""){
      # Ignore lines beginning with "#"
      IF($ArrayLine.substring(0,1) -ne "#"){
        IF($AllowHash){$ProcessedArray += $ArrayLine}
        # Remove comments at the end of line
        ELSE{$ProcessedArray += $ArrayLine.Split("#")[0].Trim()}}}}
  , $ProcessedArray
}
FUNCTION Process7MMTinputFile {
  Param([String]$InputFile,[String]$Description,[Switch]$AllowHash)
  IF(!(Test-Path $InputFile)){Wr "Path test to $InputFile failed" RED;EXITING}
  [System.Array]$GetFile = Get-Content $InputFile
  # Allow blank input file
  IF(!$GetFile){Wr "$InputFile is empty!" YELLOW;Wr;Wr }
  Wr "Loaded $Description from $InputFile successfully!" GREEN;Wr
  IF($AllowHash){$GetFile = ProcessArray $GetFile -AllowHash}
  ELSE{$GetFile = ProcessArray $GetFile}
  , $GetFile
}

## CONNECT TO 7-MODE/CDOT CLUSTER FUNCTION ##
## ======================================= ##

FUNCTION Get-PsCredential{
  Param([String]$User,[String]$Prompt)
  Wr $Prompt CYAN;$P = Read-Host -AsSecureString;Wr
  RETURN (New-Object System.Management.Automation.PsCredential($User,$P))
}
FUNCTION Load-DataOntapPSTK{
  IF(Get-Module DataONTAP){RETURN}
  [Void](Import-Module DataONTAP -ErrorAction SilentlyContinue)
  IF(!(Get-Module DataONTAP)){Wr "Unable to load the DataONTAP PSTK" RED;EXITING}
  Wr "Loaded the DataONTAP PSTK!" GREEN;Wr;Wr
}
FUNCTION Connect-ToNetApp{
  Param([String]$Name,[System.Object]$Cred,[Switch]$SevenMode)
  [Void](Load-DataOntapPSTK)
  Wr "Checking connection to $Name ..." CYAN;Wr
  IF($SevenMode){
    $C = Connect-NaController -Name $Name -Credential $Cred -Timeout 20000 -ErrorAction SilentlyContinue
  }ELSE{
    $C = Connect-NcController -Name $Name -Credential $Cred -Timeout 20000 -ErrorAction SilentlyContinue
  }
  IF($C){Wr "Successfully connected to $Name" GREEN;Wr;Wr}
  ELSE{Wr "Unable to connect to $Name" RED;EXITING}
}

#############################
## PART 1: DATA COLLECTION ##
#############################

Wr;Wr "<<<<<<<<<< U7C: Usermap7toC $Version >>>>>>>>>>" MAGENTA;Wr
Wr;Wr ">>>>>>> PART 1: DATA COLLECTION <<<<<<<" MAGENTA;Wr;Wr
[System.Array]$Systems7G = @()
[System.Object]$vFilerList = @{}
[System.Array]$7MFFcontent = Process7MMTinputFile $7ModeFilersFile  "7-Mode Filers File"

## PROCESS THE 7-MODE FILERS FILE ##
Wr;Wr ">>> PROECESSING 7-MODE FILERS FILE <<<" GREEN;Wr;Wr
FOREACH ($line in $7MFFcontent){
  $lineSplit = $line.split(",")
  IF($lineSplit.count -eq 2){
    # Valid input has 2 CSV columns
    [String]$pFiler = $lineSplit[0]
    [String]$vFiler = $lineSplit[1]
  }
  IF($pFiler -and $vFiler){
    # Valid input requires pFiler and vFiler!
    IF($Systems7G -contains $pFiler){}
    ELSE{
      $Systems7G += $pFiler
      [System.Object]$vFilerList.$pFiler = @{}
      [System.Array]$vFilerList.$pFiler.vFilers = @()
    }
    IF($vFilerList.$pFiler.vFilers -contains $vFiler){}
    ELSE{
      $vFilerList.$pFiler.vFilers += $vFiler
      Wr "pFiler: " CYAN;Wr $pFiler;Wr " - vFiler: " CYAN;Wr $vFiler;Wr
    }  
  }
}
IF(!$Systems7G){Wr;Wr "Error processing the 7-Mode Filers File $7ModeFilersFile" RED; EXITING}

FUNCTION Get-7MConfigFile{
  Param([String]$ETCPATH,[String]$7MConfigFile,[String]$ConfigDirectory)
  Wr "Reading the file: " GREEN;Wr $7MConfigFile;Wr        
  [String]$StringOutput = Read-NaFile ($ETCPATH + "/$7MConfigFile")
  [System.Array]$ArrayOutput = $StringOutput.Split("`n")
  $ArrayOutput | Set-Content ($ConfigDirectory + "/" + $7MConfigFile)     
}

## 7-Mode: ONLINE MODE ##
## =================== ##

Wr;[Boolean]$OnlineFor7Mode = ProcessYesNoSwitch "Online acquisition of 7-Mode Config Files (yes/no)? ";Wr
IF($OnlineFor7Mode){
  $UserName7Mode = PromptSetting "7-Mode Username: "
  $Credential7M  = Get-PsCredential $UserName7Mode "7-Mode Password: "
  $Systems7G | FOREACH {
    [Void](Connect-ToNetApp $_ $Credential7M -SevenMode)                  
    FOREACH($vFiler in $vFilerList.$_.vFilers){
      [String]$GetRootVol = ""; [String]$ETCPATH = ""
      [String]$SaveFolder = ($_ + "." + $vFiler)
      [Void](New-Item -Path $SaveFolder -ItemType Directory -Force)
      IF($vFiler -eq "vFiler0"){$global:CurrentNaController.vfiler = ""}
      ELSE{$global:CurrentNaController.vfiler = $vFiler}
      $GetRootVol = (Get-NaVolRoot -ErrorAction SilentlyContinue).Name
      IF($GetRootVol){$ETCPATH = "/vol/" + $GetRootVol + "/etc"}
      ELSE{$ETCPATH = (Get-NaCifsShare -Share ETC$).MountPoint }   
      IF(!$ETCPATH){Wr;Wr "Unable to determine $_ $vFiler 's /etc path" RED;EXITING}
      Wr "ETC path for 7GSystem = $_ & vFiler = $vFiler is " GREEN;Wr $ETCPATH YELLOW;Wr
      [Void](Get-7MConfigFile $ETCPATH "group" $SaveFolder)
      [Void](Get-7MConfigFile $ETCPATH "passwd" $SaveFolder)
      [Void](Get-7MConfigFile $ETCPATH "usermap.cfg" $SaveFolder);Wr
    }
  }
}

## PRIMARY CDOT CLUSTER & SVM ##
## ========================== ##

Wr;Wr ">>> PRIMARY cDOT CLUSTER & SVM <<<" GREEN;Wr;Wr
$Cluster = PromptSetting "Enter the Cluster: "
$SVM     = PromptSetting "Enter the SVM    : ";Wr
[Boolean]$OnlineForCDOT = ProcessYesNoSwitch "Online cDOT data acquisition (yes/no)? ";Wr
IF($OnlineForCDOT){
  $UserNameCdot = PromptSetting "Cluster User: "
  $Credential = Get-PsCredential $UserNameCdot "Cluster Password: "
  [Void](Connect-ToNetApp $Cluster $Credential)
}

## FUNCTION: COLLECT REQUIRED 7G CONFIG FILES (per vFiler) ##
## ======================================================= ##

FUNCTION GetConfigFile {
  Param([String]$7GcfgFile,[String]$7GSystem,[String]$vFilerName)
  Wr ("Looking for $7GcfgFile - for $7Gsystem & $vFilerName - in folder(s):") GREEN;Wr
  Wr ($7Gsystem + "." + $vFilerName) YELLOW
  IF($vFilerName -eq "vFiler0"){Wr " ";Wr ($7Gsystem) YELLOW };Wr
  IF(Test-Path ($7Gsystem + "." + $vFilerName + "\" + $7GcfgFile)){
    $ArrayOutput = Get-Content ($7Gsystem + "." + $vFilerName + "\" + $7GcfgFile)
  }ELSEIF(($vFilerName -eq "vFiler0") -and (Test-Path ($7Gsystem + "\" + $7GcfgFile))){
    $ArrayOutput = Get-Content ($7Gsystem + "\" + $7GcfgFile)
  }ELSE{Wr "Unable to find file"RED;EXITING} # USE BLANK FILES IF NO DATA!
  IF(!$ArrayOutput){Wr "$7GcfgFile file at $cfgFilePath is a blank file!" YELLOW;Wr;Wr}
  , $ArrayOutput
}

## INITIALIZE VARIABLES ##
[System.Object]$PasswdFile   = @{} # Passwd file in $PasswdFile.$7GSystem.$vFiler
[System.Object]$GroupFile    = @{} # Group file in $GroupFile.$7GSystem.$vFiler
[System.Object]$UsermapFile  = @{} # Usermap file in $UsermapFile.$7GSystem.$vFiler
[System.Object]$CdotUU       = @{} # Init. Cdot Unix Users - OBJECT
[System.Array]$CdotUU.Users  = @() # Init. " - Users ARRAY
[System.Array]$CdotUU.UIDs   = @() # Init. " - UIDs  ARRAY
[System.Object]$CdotUU.User  = @{} # Init. " - User  OBJECT
[System.Object]$CdotUU.UID   = @{} # Init. " - UID   OBJECT
[System.Object]$CdotUG       = @{} # Init. Cdot Unix Groups - OBJECT
[System.Array]$CdotUG.Groups = @() # Init. " - Groups ARRAY
[System.Array]$CdotUG.GIDs   = @() # Init. " - GIDs   ARRAY
[System.Object]$CdotUG.Group = @{} # Init. " - Group  OBJECT
[System.Object]$CdotUG.GID   = @{} # Init. " - GID    OBJECT

## USERMAP COLLECTION and PROCESSING ##
## ================================= ##

Wr;Wr ">>> USERMAP COLLECTION and PROCESSING <<<" GREEN;Wr;Wr
Wr "7-Mode group file -> CDOT Unix Groups" GREEN;Wr
Wr "7-Mode passwd file -> CDOT Unix Users" GREEN;Wr        
Wr "7-Mode usermap.cfg file -> CDOT Name Mappings" GREEN;Wr;Wr
Wr; Wr ">>> COLLECT passwd/group/usermap.cfg FILES <<<" GREEN;Wr;Wr
$Systems7G | FOREACH{
  [System.Object]$PasswdFile.$_  = @{}
  [System.Object]$GroupFile.$_   = @{}
  [System.Object]$UsermapFile.$_ = @{}
  FOREACH($vFiler in ($vFilerList.$_.vFilers)){
    $PasswdFile.$_.$vFiler = ProcessArray (GetConfigFile "passwd" $_ $vFiler)
    Wr "passwd file for $_ vfiler $vfiler :" GREEN;Wr;Wr
    FOREACH($line in ($PasswdFile.$_.$vFiler)){Wr $line YELLOW;Wr};Wr
    $GroupFile.$_.$vFiler = ProcessArray (GetConfigFile "group" $_ $vFiler)
    Wr "group file for $_ vfiler $vfiler :" GREEN;Wr;Wr
    FOREACH($line in ($GroupFile.$_.$vFiler)){Wr $line YELLOW;Wr};Wr
    $UsermapFile.$_.$vFiler = ProcessArray (GetConfigFile "usermap.cfg" $_ $vFiler)
    Wr "usermap.cfg file for $_ vfiler $vfiler :" GREEN;Wr;Wr
    FOREACH($line in ($UsermapFile.$_.$vFiler)){Wr $line YELLOW;Wr};Wr
  }
}

## GET passwd/group/usermap.cfg CONFIGURATIONS FROM CDOT ##
## ====================================================== ##
Wr;Wr ">>> GET passwd/group/usermap.cfg CONFIGURATIONS FROM CDOT <<<" GREEN;Wr;Wr

## >> PROCESSING CDOT UNIX USERS << ##
IF($OnlineForCDOT){
  $GetNcNameMappingUU = Get-NcNameMappingUnixUser -VserverContext $SVM
  [System.Array]$CdotUnixUsers = @()
  $GetNcNameMappingUU | FOREACH{$CdotUnixUsers += ($_.Vserver + "," + $_.UserName + "," + $_.UserId + "," + $_.GroupID)}
  $CdotUnixUsers | Set-Content $CdotUnixUsersFile
}ELSE{
  [System.Array]$CdotUnixUsers = Process7MMTinputFile $CdotUnixUsersFile "CDOT Unix User file";Wr
}
FOREACH($line in $CdotUnixUsers){
  Wr $line YELLOW;Wr
  [System.Array]$CommaSplit = $line.Split(",")                  
  [String]$User = $CommaSplit[1]                  
  [String]$UID  = $CommaSplit[2]                  
  [String]$GID  = $CommaSplit[3]                  
  [System.Object]$CdotUU.User.$User = @{}                       
  [String]$CdotUU.User.$User.UID = $UID                  
  [String]$CdotUU.User.$User.GID = $GID                  
  [System.Object]$CdotUU.UID.$UID = @{}                        
  [String]$CdotUU.UID.$UID.User = $User                 
  [String]$CdotUU.UID.$UID.GID  = $GID                   
  $CdotUU.Users += $User
  $CdotUU.UIDs  += $UID
};Wr

## >> PROCESSING CDOT UNIX GROUPS << ##
IF($OnlineForCDOT){
  $GetNcNameMappingUG = Get-NcNameMappingUnixGroup -VserverContext $SVM
  [System.Array]$CdotUnixGroups = @()
  $GetNcNameMappingUG | Foreach{$CdotUnixGroups += ($_.Vserver + "," + $_.GroupName + "," + $_.GroupId)}
  $CdotUnixGroups | Set-Content $CdotUnixGroupsFile
}ELSE{
  [System.Array]$CdotUnixGroups = Process7MMTinputFile $CdotUnixGroupsFile "CDOT Unix Group file";Wr
}             
FOREACH($line in $CdotUnixGroups){
  Wr $line YELLOW;Wr
  [System.Array]$CommaSplit = $line.Split(",")
  [String]$Group = $CommaSplit[1]
  [String]$GID   = $CommaSplit[2]
  [String]$CdotUG.Group.$Group = $GID
  [String]$CdotUG.GID.$GID = $Group
  $CdotUG.Groups += $Group
  $CdotUG.GIDs   += $GID
};Wr

## >> PROCESSING CDOT NAMEMAPPINGS << ##
IF($OnlineForCDOT){
  $GetNcNameMap = Get-NcNameMapping -VserverContext $SVM
  [System.Array]$CdotNameMappings = @()
  $GetNcNameMap | FOREACH {$CdotNameMappings += ($_.Vserver + "," + ($_.Direction).replace("_","-") + "," + $_.Position + "," + $_.Pattern + "," + $_.Replacement + ",")}
  $CdotNameMappings | Set-Content $CdotNameMappingFile
}ELSE{
  [System.Array]$CdotNameMappings = Process7MMTinputFile $CdotNameMappingFile "CDOT Name-Mapping file";Wr
}
FOREACH($line in $CdotNameMappings){Wr $line YELLOW;Wr};Wr

########################################
## PART 2: GENERATING COMMANDS OUTPUT ##
########################################

Wr;Wr ">>>>>>> PART 2: GENERATING COMMANDS OUTPUT <<<<<<<" MAGENTA;Wr
Wr;Wr "Clustershell Output File = " CYAN;Wr $OutputFileName;Wr;Wr
FUNCTION Cshell{("# " + $ClusterName + "::>"),("")}

## UNIX: PROCESS UNIX GROUPS & USERS ##
## ================================= ##

[System.Array]$UnixGroupWarningOutput = @()
[System.Array]$CdotUG.NewGroups = @()
[System.Array]$UnixUserWarningOutput  = @()
[System.Array]$CdotUU.NewUsers = @()

$Systems7G | FOREACH{
  FOREACH($vFiler in ($vFilerList.$_.vFilers)){
   
    ## >> PROCESS group FILE << ##                  
    FOREACH($line in $GroupFile.$_.$vFiler ){
      [System.Array]$GroupLine = $line.Split(":")
      [String]$UnixGroup = $GroupLine[0]
      [String]$UnixGID = $GroupLine[2]
      IF($CdotUG.Groups -Contains $UnixGroup){
        IF($CdotUG.Group.$UnixGroup -ne $UnixGID){
          $UnixGroupWarningOutput += ("# ++ WARNING ++ UNIX GROUP CONFLICT: Group $UnixGroup exists in SVM $SVM but has GID " + $CdotUG.Group.$UnixGroup + " and not $UnixGID as in the group file for $_ $vFiler !")}
      }ELSEIF($CdotUG.GIDs -Contains $UnixGID){
        IF($CdotUG.GID.$UnixGID -ne $UnixGroup){
          $UnixGroupWarningOutput += ("# ++ WARNING ++ UNIX GROUP CONFLICT: GID $UnixGID exists in SVM $SVM but with Group Name " + $CdotUG.GID.$UnixGID + " and not $UnixGroup as in the group file for $_ $vFiler !")}
      }ELSE{
        $CdotUG.NewGroups += $UnixGroup
        $CdotUG.Groups    += $UnixGroup
        $CdotUG.GIDs      += $UnixGID
        $CdotUG.Group.$UnixGroup = $UnixGID
        $CdotUG.GID.$UnixGID = $UnixGroup
      }
    }
   
    ## >> PROCESS passwd FILE << ##                 
    FOREACH($line in $PasswdFile.$_.$vFiler){
      [System.Array]$PasswdLine = $line.Split(":")
      [String]$UnixUser = $PasswdLine[0]
      [String]$UnixUID  = $PasswdLine[2]
      [String]$UnixGID  = $PasswdLine[3]
      [String]$UnixFullName = $PasswdLine[4]
      IF(($UnixGID -eq "") -and !($CdotUU.Users -Contains $UnixUser)){
        $UnixUserWarningOutput += ("# ++ WARNING ++ UNIX USER HAS NO GID: Unix User $UnixUser with UID $UnixUID has no GID in the Passwd file for $_ $vFiler !")
      }ELSEIF($CdotUU.Users -Contains $UnixUser){
        IF($CdotUU.User.$UnixUser.UID -ne $UnixUID){
          $UnixUserWarningOutput += ("# ++ WARNING ++ UNIX USER CONFLICT: Unix User $UnixUser exists in SVM $SVM but has UID " + $CdotUU.User.$UnixUser.UID + " and not $UnixUID as in the Passwd for $_ $vFiler !")}
        IF(($CdotUU.User.$UnixUser.GID -ne $UnixGID) -and ($UnixGID -ne "")){
          $UnixUserWarningOutput += ("# ++ WARNING ++ UNIX USER CONFLICT: Unix User $UnixUser exists in SVM $SVM but has GID " + $CdotUU.User.$UnixUser.GID + " and not $UnixGID as in the Passwd for $_ $vFiler !")}
      }ELSEIF($CdotUU.UIDs -Contains $UnixUID){                           
        IF($CdotUU.UID.$UnixUID.User -ne $UnixUser){
          $UnixUserWarningOutput += ("# ++ WARNING ++ UNIX UID CONFLICT: Unix UID $UnixUID exists in SVM $SVM but has Unix User " + $CdotUU.UID.$UnixUID.User + " and not $UnixUser as in the Passwd for $_ $vFiler !")}
        IF(($CdotUU.UID.$UnixUID.GID  -ne $UnixGID) -and ($UnixGID -ne "")){
          $UnixUserWarningOutput += ("# ++ WARNING ++ UNIX UID CONFLICT: Unix UID $UnixUID exists in SVM $SVM but has GID " + $CdotUU.UID.$UnixUID.GID + " and not $UnixGID as in the Passwd for $_ $vFiler !")}
      }ELSE{                         
        $CdotUU.NewUsers += $UnixUser
        $CdotUU.Users    += $UnixUser
        $CdotUU.UIDs     += $UnixUID
        [System.Object]$CdotUU.User.$UnixUser = @{}
        $CdotUU.User.$UnixUser.UID = $UnixUID
        $CdotUU.User.$UnixUser.GID = $UnixGID
        $CdotUU.User.$UnixUser.FullName = $UnixFullName
        [System.Object]$CdotUU.UID.$UnixUID = @{}
        $CdotUU.UID.$UnixUID.User = $UnixUser
        $CdotUU.UID.$UnixUID.GID  = $UnixGID
       
        ## Fix for Burt 751845 (where a UNIX group must exist for every Unix User's specified GID) ##
        IF(!($CdotUG.GIDs -Contains $UnixGID)){
          IF($SVM.length -eq 13){[String]$StdUnixGroupPrefix = $SVM.substring(0,2) + $SVM.substring(5,8)}
          ELSE{[String]$StdUnixGroupPrefix = $SVM}
          $UnixGroup = ($StdUnixGroupPrefix + "_GID" + $UnixGID)
          $CdotUG.NewGroups += $UnixGroup
          $CdotUG.Groups   += $UnixGroup
          $CdotUG.GIDs    += $UnixGID
          $CdotUG.Group.$UnixGroup = $UnixGID
          $CdotUG.GID.$UnixGID = $UnixGroup
        }
      }
    }                 
  }
}

FUNCTION isNumeric($x){$x2=0;$isNum=[System.Int32]::TryParse($x,[ref]$x2);RETURN $isNum}
FUNCTION OUTPUT-UnixGroups{
  Param([String]$ClusterName,[String]$SVMname)
  Uline ("## UNIX GROUPS ##");$UnixGroupWarningOutput;Cshell
  $CdotUG.NewGroups | FOREACH{
    IF(!(isNumeric($CdotUG.Group.$_))){
      ("# ++ WARNING ++ The GID in the next line not numeric - suspect issues in the passwd file!")
      ("# unix-group create -vserver $SVMname -name $_ -id " + $CdotUG.Group.$_)
    }ELSE{
      ("unix-group create -vserver $SVMname -name $_ -id " + $CdotUG.Group.$_)
    }
  };("")
}

FUNCTION OUTPUT-UnixUsers{
  Param([String]$ClusterName,[String]$SVMname)
  Uline ("## UNIX USERS ##");$UnixUserWarningOutput;Cshell
  $CdotUU.NewUsers | FOREACH{
    If(!(isNumeric($CdotUU.User.$_.GID))){
      ("# ++ WARNING ++ The GID in the next line not numeric - suspect issues in the passwd file!")
      ("# unix-user create -vserver $SVMname -user $_ -id " + $CdotUU.User.$_.UID + " -primary-gid " + $CdotUU.User.$_.GID)
    }ELSE{
      ("unix-user create -vserver $SVMname -user $_ -id " + $CdotUU.User.$_.UID + " -primary-gid " + $CdotUU.User.$_.GID + " -full-name " + '"' + $CdotUU.User.$_.FullName + '"')
    }
  };("")
}

## UNIX: PROCESS USERMAP ##
## ===================== ##

FUNCTION OUTPUT-UserMap{
  Param([String]$ClusterName,[String]$SVMname)
  Uline ("## USERMAP ##");Cshell;("set -confirmations off");("")
 
  ## GET START NAMEMAP INDEX(s) ##
  [Int]$IndexWinUnix = 0
  [Int]$IndexUnixWin = 0
  FOREACH($NMline in $CdotNameMappings){
    $NMline = $NMline.split(",")
    IF($NMline[1] -eq "win-unix"){
      IF([Int]$NMline[2] -gt $IndexWinUnix){$IndexWinUnix = [Int]$NMline[2]}}
    IF($NMline[1] -eq "unix-win"){
      IF([Int]$NMline[2] -gt $IndexUnixWin){$IndexUnixWin = [Int]$NMline[2]}}
  }
 
  ## GENERATED NAME MAPS CLUSTERSHELL ##
  $Systems7G | FOREACH{
    FOREACH($vFiler in ($vFilerList.$_.vFilers)){
      FOREACH($line in $UsermapFile.$_.$vFiler){
        [String]$NewLine = $line.replace("=>","#").replace("<=","#").replace("==","#")
        [System.Array]$lineSplit = $NewLine.Split("#")
        IF($LineSplit.Count -eq 1){"# ++ WARNING ++ This line has no =>/<=/==: $line"}
        ELSEIF(!$lineSplit[0]){"# ++ WARNING ++ This line has no NT User: $line"}
        ELSEIF(!$lineSplit[1]){"# ++ WARNING ++ This line has no UNIX User: $line"}
        ELSE{
          [String]$NTuser7G = $lineSplit[0].trim().replace('"','')
          [String]$UXuser7G = $lineSplit[1].trim().replace('"','')
          $NTuser7G = $NTuser7G.replace("\","\\")
          IF(!$NTuser7G -or !$UXuser7G){
            "# ++ WARNING ++ CDOT does not support pattern/replacement " + '""' + ": $line"
          }ELSE{
            [String]$WinUnixErr = [String]$UnixWinErr = $null
            [Boolean]$MatchWinUnix = [Boolean]$MatchUnixWin = $false
            [Boolean]$DirWinUnix = [Boolean]$DirUnixWin = $false
            IF($line -match "=>"){$DirWinUnix = $true}
            IF($line -match "<="){$DirUnixWin = $true}
            IF($line -match "=="){$DirWinUnix = $DirUnixWin = $true}
            IF($line.split(":").count -gt 1){"# ++ CAUTION ++ IP/Subnet Name Mapping Detected: $line"}
           
            FOREACH($NMline in $CdotNameMappings){
              $NMline = $NMline.split(",")
              IF(!$MatchWinUnix -and $DirWinUnix -and ($NMline[1] -eq "win-unix")){
                IF(($NMline[3] -eq $NTuser7G)){
                  IF($NMline[4] -eq $UXuser7G){$MatchWinUnix = $true}
                  ELSE{$WinUnixErr = $NMline[4]}
                }
              }
              IF(!$MatchUnixWin -and $DirUnixWin -and ($NMline[1] -eq "unix-win")){
                IF(($NMline[3] -eq $UXuser7G)){
                  IF($NMline[4] -eq $NTuser7G){$MatchUnixWin = $true}
                  ELSE{$UnixWinErr = $NMline[4]}
                }
              }
            }
           
            #> OUTPUT WIN-UNIX NAME MAPPING COMMAND <#
            IF(!$MatchWinUnix -and $DirWinUnix){
              $IndexWinUnix++;[String]$hash = ""
              IF($WinUnixErr){("# ++ WARNING ++ NAME MAPPING CONFLICT: NT User $NTuser7G is already mapped to UNIX User " + $WinUnixErr + ", but $_ $vFiler wants to map to $UXuser7G");$hash = "# "}
              IF($hash -eq ""){$CdotNameMappings  += ($SVMname + ",win-unix," + $IndexWinUnix + "," + $NTuser7G + "," + $UXuser7G + ",")}
             
              # ONTAP 9+ SECTION: START #
              # This section handles subnet or host qualified usermaps.
              [String]$IP_SN_HOST = ""
              IF($NTuser7G.Split(":").count -eq 2){
                $IP_SN_HOST = $NTuser7G.split(":")[0]
                $NTuser7G   = $NTuser7G.split(":")[1]
                IF($IP_SN_HOST.Split(".").count -eq 4){$IP_SN_HOST = " -address $IP_SN_HOST"}
                ELSE{$IP_SN_HOST = " -hostname $IP_SN_HOST"}
              }
              # ONTAP 9+ SECTION: END
             
              ($hash + "vserver name-mapping create -vserver $SVMname -direction win-unix -position $IndexWinUnix -pattern " + '"' + $NTuser7G + '"' + " -replacement " + '"' + $UXuser7G + '"' + $IP_SN_HOST)
            }
           
            #> OUTPUT UNIX-WIN NAME MAPPING COMMAND <#
            IF(!$MatchUnixWin -and $DirUnixWin){
              $IndexUnixWin++;[String]$hash = ""
              IF($UnixWinErr){("# ++ WARNING ++ NAME MAPPING CONFLICT: UNIX User $UXuser7G is already mapped to NT User " + $UnixWinErr + ", but $_ $vFiler wants to map to $NTuser7G");$hash = "# "}
              IF($hash -eq ""){$CdotNameMappings  += ($SVMname + ",unix-win," + $IndexUnixWin + "," + $UXuser7G + "," + $NTuser7G + ",")}
             
              # ONTAP 9+ SECTION: START #
              # This section handles subnet or host qualified usermaps.                                                 
              [String]$IP_SN_HOST = ""
              IF($UXuser7G.Split(":").count -eq 2){
                $IP_SN_HOST = $UXuser7G.split(":")[0]
                $UXuser7G   = $UXuser7G.split(":")[1]
                IF($IP_SN_HOST.Split(".").count -eq 4){$IP_SN_HOST = " -address $IP_SN_HOST"}
                ELSE{$IP_SN_HOST = " -hostname $IP_SN_HOST"}
              }
              # ONTAP 9+ SECTION: END
              
              ($hash + "vserver name-mapping create -vserver $SVMname -direction unix-win -position $IndexUnixWin -pattern " + '"' + $UXuser7G + '"' + " -replacement " + '"' + $NTuser7G + '"' + $IP_SN_HOST)
            }
          }
        }
      }
    }
  };("");("set -confirmations on");("")
}      

####################
## PART 3: OUTPUT ##
####################

[System.Array]$Output = @()
$Output += Uline ("## Usermap 7 to C (U7C): Commands generated on " + (Get-Date -uformat %d%h%G) + " ##")
$Output += ("# CAVEAT UTILITOR! This tool comes with no support and no warranty, please REVIEW, SANITY CHECK, and UNDERSTAND all commands before application!")
$Output += ("# ++ CAUTION ++ Support for address/hostname vserver name-mappings is limited!"),("")
$Output += OUTPUT-UnixGroups $Cluster $SVM
$Output += OUTPUT-UnixUsers $Cluster $SVM
$Output += OUTPUT-UserMap $Cluster $SVM
$Output | Set-Content $OutputFileName;Notepad $OutputFileName
PressEnterToContinue