How to Report on CDOT Shares, Shares ACLs, and NTFS Security

Introduction

The following script is a little something I cooked up to do an audit of all the shares on a Clustered Data ONTAP Cluster (or Clusters). It creates an Excel report with CIFS shares, share ACLs, and NTFS Security information on the folder at the root of the share (includes owner, DACLs, and SACLs). It has a bonus feature in that it detects shares which have had their root folder deleted.

The framework of the script can easily be manipulated to generate Excel reports on other things.

Notes

1) The standard readonly role is not quite sufficient for running Get-NcFileDirectorySecurity. To make/modify your readonly role so that it can be used to run this script, apply the below via the Clustershell ::>

security login role create CORP_readonly -cmddirname DEFAULT -Access readonly
security login role create CORP_readonly -cmddirname security  -Access none
security login role create CORP_readonly -cmddirname "security login password"  -Access all
security login role create CORP_readonly -cmddirname set  -Access all
security login role create CORP_readonly -cmddirname "vserver security file-directory show"  -Access all
security login create -username readonly -application ontapi -role CORP_readonly -authmethod password

2) To enable running the script from a network share, you need to enable the below (if your permissions allow) as detailed here:


 To enable client-side SSP for winrm, run the following line:

PS> Enable-WSManCredSSP -Role client -DelegateComputer *

If your corporate laptop has folder re-direction, placing the Data ONTAP PowerShell Toolkit folder (DataONTAP) in your downloads folder should work since this is rarely re-directed.

The Script

Formatted for blogger!

Copy and paste the following into a text editor, and save as say “CDOT_Share_ACL_NTFS_Reporter.PS1” (the name doesn’t matter), then either right-click the file and “Run with PowerShell”, or run in PowerShell as:

PS> .\ CDOT_Share_ACL_NTFS_Reporter.PS1

##########################################################
## CDOT Share, Share ACL, and NTFS Permissions Reporter ##
##########################################################

Param(
  [String]$PSTKpath,     # If DataONTAP is not in the default location or ...\Downloads\DataONTAP, this overrides the prompt
  [String]$ClusterName,  # If not Clusters.TXT file, this overrides the prompt for a Cluster Name
  [String]$UserName,     # Used to override prompt for User Name for Cluster connect
  [Switch]$NoCachedCreds # Used to override looking for Cached Credentials
);""

## GENERIC DISPLAY FUNCTIONS ##

[System.Object]$Color = @{}; "W=White","G=Green","Y=Yellow","R=Red","C=Cyan","M=Magenta" | Foreach { [String]$Color.($_.Split("=")[0]) = $_.Split("=")[1] }
Function Wr { Param ([Switch]$N); $i = 0; While ($i -lt $Args.Count){ Write-Host $Args[$i+1] -ForegroundColor $Color.($Args[$i]) -NoNewLine; $i+=2 }; If(!$N){ Write-Host } }
Function Countdown { Param ([Int]$Count); while ($Count -gt 0){ Wr R "$Count " -N; sleep 1; $Count-- } }

## STEP 1: LOAD THE DATA ONTAP POWERSHELL TOOLKIT ##
## ============================================== ##

Wr G "1) Loading the Data ONTAP PowerShell Toolkit ..."; ""
[Void](Import-Module DataONTAP -ErrorAction SilentlyContinue)
If(!(Get-Module DataONTAP)){
  [Void](Import-Module ([Environment]::GetFolderPath("UserProfile") + "\Downloads\DataONTAP") -ErrorAction SilentlyContinue)
}
If(!(Get-Module DataONTAP)){
  Wr C "Enter path to the DataONTAP folder: " -N
  If($PSTKpath){ Wr W $PSTKpath } else { $PSTKpath = Read-Host }
  [Void](Import-Module $PSTKpath -ErrorAction SilentlyContinue); ""
}
If(!(Get-Module DataONTAP)){
  Wr R "Unable to load the DataONTAP PowerShell Toolkit. Exiting after 10 seconds!"; "";
  Countdown 10; ""; ""; Exit
}
Wr G "Loaded the Data ONTAP PowerShell Toolkit!"; ""

## STEP 2: GET THE CLUSTERS ##
## ======================== ##

Wr G "2) Get the Cluster(s) ..."; ""
[System.Array]$Clusters = @()
If(Test-Path Clusters.txt){
  [System.Array]$ClustersTXT = Get-Content Clusters.txt
  $ClustersTXT | foreach {
    #> Record Clusters ignoring blank and comments (#) lines <#
    If ( !($_ -eq "") -and !($_.StartsWith("#")) ){ $Clusters += $_ }
  }   
} else {
  Wr C "Enter the cluster to connect to: " -N
  If ($ClusterName){ Wr W $ClusterName } else { $ClusterName = Read-Host }
  $Clusters += $ClusterName; ""
}

## STEP 3: CREDENTIALS TO CONNECT ##
## ============================== ##

Wr G "3) Credentials to connect ..."; ""

#> 3.1: Generate FileName for saved credentials file <#
$whoAmI   = (whoAmI).ToUpper()
$SplitMe  = $whoAmI.Split("\")
If($SplitMe.Count -eq 2){ $User = $SplitMe[0] + $SplitMe[1] }
else {                    $User = $whoAmI }
$filePath = $User + ".cred"
$test     = Test-Path $filePath
If($NoCachedCreds){$test = $null}

#> 3.2: Acquire credentials <#
[System.Array]$CredsFile = @()
If(!$test){ # ... if no credentials file, prompt and save one
  Wr C "Enter username for account to log into the Clusters [$whoAmI]: " -N
  If($UserName){ Wr W $UserName } else { $Username = Read-Host }
  If($UserName -eq ""){$UserName = $whoAmI}
  Wr C "Password: " -N
  $Password     = Read-Host -AsSecureString
  $SecureString = $Password | ConvertFrom-SecureString
  $CredsFile   += $Username
  $CredsFile   += $SecureString
  $CredsFile | Out-File -FilePath $filePath
} else { # ... otherwise get credentials from the file
  Wr G "Obtaining credentials from file $filePath ..."
  $CredsFile    = Get-Content $filePath
  $Username     = $CredsFile[0]
  $Password     = $CredsFile[1] | ConvertTo-SecureString
};""
$Credential = New-Object System.Management.Automation.PsCredential($Username,$Password)

## STEP 4: TEST THE CREDENTIALS WORK ##
## ================================= ##

Wr G "4) Checking connection to Cluster(s) ..."; ""
$Clusters | foreach {
  Wr C "Checking connection to " Y "$_" C " ..."; ""
  $Connect = Connect-NcController -Name $_ -Credential $Credential -ErrorAction SilentlyContinue      
  If (!$Connect){
    Wr R "Unable to connect to " Y "$_" R " with provided credentials. Removing credentials file. Exiting after 10 seconds!"; ""
    Remove-Item -Path $filePath
    Countdown 10; ""; ""; exit
  } else {
    Wr G "Successfully connected to $_ "; ""
  }
}

## STEP 5: GET THE REQUIRED INFORMATION - CIFS SHARES, ACLS, NTFS PERMISSIONS ##
## ========================================================================== ##

Wr G "5) Acquiring Shares, ACLs and NTFS Permissions ..."; ""
[System.Object]$Data = @{} # Data Hashtable
[Int]$Index          = 1
$Clusters | foreach {
  [Void](Connect-NcController -Name $_ -Credential $Credential -ErrorAction SilentlyContinue)
 
  #> Get Data SVMs <#
  $vsQuery             = Get-NcVserver -Template
  $vsQuery.VserverType = "data"
  $SVMs                = (Get-NcVserver -Query $vsQuery).Vserver
 
  #> Cycle through Data SVMs <#
  Foreach ($SVM in $SVMs){
    Wr G "Processing CIFS Shares for Cluster $_ and SVM $SVM ..."; ""
    $Attrs           = Get-NcCifsShare -Template
    $Attrs.ShareName = ""
    $Attrs.Path      = ""
    $Attrs.Acl       = ""
    $CifsShares      = Get-NcCifsShare -Attributes $Attrs -VserverContext $SVM
    $CifsSharesCount = $CifsShares.Count
   
    #> Cycle through the CIFS Shares on the SVM <#
    Foreach ($Share in $CifsShares){
      Wr C "$CifsSharesCount " -N
      [System.Object]$Data.$Index    = @{}                # Data.Index Hashtable
      [String]$Data.$Index.Cluster   = $_                 # + Cluster Name
      [String]$Data.$Index.Vserver   = $SVM               # + SVM
      [String]$Data.$Index.ShareName = $Share.ShareName   # + Share Name
      [String]$Data.$Index.Path      = $Share.Path        # + Share Path
      [System.Array]$Data.$Index.Acl = $Share.Acl         # + Share ACL(s)
      [Int]$Data.$Index.ACLs         = ($Share.Acl).Count # + Share ACL(s) count
     
      #> Processing NTFS Security Descriptor <#
      [System.Array]$NTFSsec = @()
      If ($Share.Path -ne "/"){
        [System.Array]$NTFSsec = (Get-NcFileDirectorySecurity -Path ($Share.Path) -LookupNames -VserverContext $SVM -ErrorAction SilentlyContinue).acls
        If($NTFSsec){
          [System.Array]$DACLs = @()
          [Int]$DACL_rows = 0
          Foreach ($line in $NTFSsec){
            If     ($line.StartsWith("NTFS Security Descriptor")){}
            elseif ($line.StartsWith("Control:")){ $Data.$Index.Control = $line.substring(8) }
            elseif ($line.StartsWith("Owner:"))  { $Data.$Index.Owner   = $line.substring(6) }
            elseif ($line.StartsWith("Group:"))  { $Data.$Index.Group   = $line.substring(6) }
            else {
              $DACLs += $line
              $DACL_rows ++                           
            }
          }
          [System.Array]$Data.$Index.DACLs = $DACLs
          [Int]$Data.$Index.DACL_rows      = $DACL_rows
        } else {
          ""; Wr Y "Unable to Get-NcFileDirectorySecurity for share " R $Share.ShareName Y " path " R $Share.Path W " "
        }
      } elseif ( ($Share.Path -eq "/") -or !$NTFSsec ) {
        [String]$Data.$Index.Control     = ""
        [String]$Data.$Index.Owner       = ""
        [String]$Data.$Index.Group       = ""
        [System.Array]$Data.$Index.DACLs = @()
        [Int]$Data.$Index.DACL_rows      = 1
      }
     
      $CifsSharesCount --
      $Index ++
    }
    ""; ""
  }
}

## STEP 6: CONSTRUCT EXCEL WORKBOOK ##
## ================================ ##

Wr G "6) Constructing Excel Workbook ..."; ""

#> Excel Workbook - Initialize <#
Add-Type -AssemblyName Microsoft.Office.Interop.Excel
$xlFixedFormat = [Microsoft.Office.Interop.Excel.XlFileFormat]::xlWorkbookDefault
$xl            = New-Object -ComObject "Excel.Application"
$wb            = $xl.Workbooks.Add()
$xl.Worksheets.Item("Sheet2").Delete()
$xl.Worksheets.Item("Sheet3").Delete()
$ws            = $wb.ActiveSheet
$worksheets    = 1
$row           = 3
$cells         = $ws.Cells
$ws.name       = "Shares, ACLs, NTFS ..."

#> Excel Workbook - Column Headers <#
$col = 1
$cells.item($row,1).EntireRow.Interior.ColorIndex = 15
"Cluster","SVM","Share:Name","Share:Path","Share:ACL(s)","NTFS:Control","NTFS:Owner","NTFS:Group","NTFS:DACL(s)" | foreach {
  $cells.item($row,$col)           = $_
  $cells.item($row,$col).font.bold = $True
  $col++
}
$row += 2

#> Excel Workbook - Data <#
$i = 1
while ($i -lt $Index){
  Wr C ($Index - $i) C " " -N
  $cells.item($row,1) = $Data.$i.Cluster
  $cells.item($row,2) = $Data.$i.Vserver 
  $cells.item($row,3) = $Data.$i.ShareName
  $cells.item($row,4) = $Data.$i.Path
 
  #> Process Share ACLs into column <#
  If($Data.$i.ACLs -gt 0){
    $h = 0
    while($h -lt $Data.$i.ACLs){
      $cells.item($row + $h,5) = $Data.$i.Acl[$h]
      $h++
    }
  }
 
  $cells.item($row,6) = $Data.$i.Control
  $cells.item($row,7) = $Data.$i.Owner
  $cells.item($row,8) = $Data.$i.Group
 
  #> Process DACLs into column <#
  If($Data.$i.DACL_rows -gt 0){
    $j = 0
    While ($j -lt $Data.$i.DACL_rows){
      $cells.item($row + $j,9) = $Data.$i.DACLs[$j]
      $j++
    }
  }
 
  #> Accumulate row count <#
  If($Data.$i.ACLs -eq 0){ $Data.$i.ACLs = 1 } 
  If($Data.$i.ACLs -gt $Data.$i.DACL_rows){ $row += $Data.$i.ACLs } else { $row += $Data.$i.DACL_rows }
 
  $row++
  $i++
};"";""

#> Excel Workbook - Autofit Excel Columns <#
$range = $ws.UsedRange
$range.EntireColumn.Autofit() | out-null

#> Excel Workbook - Main Heading (after the Autofit) <#
$dateString      = Get-Date -uformat "%Y%m%d"
$cells.item(1,1) = "$dateString - NetApp CDOT Share, ACL, and NTFS Security Report"
$cells.item(1,1).EntireRow.font.bold           = $true
$cells.item(1,1).EntireRow.font.size           = 16
$cells.item(1,1).EntireRow.Interior.ColorIndex = 37

#> Display and Save the Excel Workbook <#
$SavePath = $Pwd.Path + "\" + $dateString + "_CDOT_Share_ACL_NTFS_Report.xlsx"
$Test     = Test-Path $SavePath
If($Test){ Remove-Item -Path $SavePath -Force }
$xl.ActiveWorkbook.SaveAs($SavePath,$xlFixedFormat)
$xl.Visible = $True
Wr G "Excel Workbook displayed and saved at " Y "$SavePath"; ""

#> Cleanup EXCEL processes <#
while ([System.Runtime.Interopservices.Marshal]::ReleaseComObject($xl)) {'cleanup Excel'}

[gc]::collect() | Out-Null;[gc]::WaitForPendingFinalizers() | Out-Null

Comments