NetAppSecCheck.ps1

<#
.NOTES
 
  Information on running PowerShell scripts can be found here:
    -http://ss64.com/ps/syntax-run.html
    -https://technet.microsoft.com/en-us/library/bb613481.aspx
 
  This script requires PowerShell 7 or later to run, information on installing or upgrading PowerShell can be found here:
    -https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-windows
 
  This script also requires that the ONTAP cluster is running 9.6 or later
 
  File Name: NetAppSecCheck.ps1
 
.DESCRIPTION
 
  The intention of this script is to provide a quick check of several security configurations
 
  Typically the following tools provide security related information for ONTAP clusters:
    - System Manager Dashboard
    - Unified Manager cluster security objectives
    - Active IQ Digital Advisor
 
  If a more thorough review is necessary of your environment, please consider contacting
  NetApp Services to request a Data Protection and Security Assessment
 
  The documents referenced in the KB article linked below should be consulted for the most up to
  date information
 
    https://kb.netapp.com/onprem/ontap/os/How_to_perform_a_security_health_check_with_a_script_in_ONTAP
    TR-4569 - https://www.netapp.com/media/10674-tr4569.pdf
    TR-4572 - https://www.netapp.com/media/7334-tr4572.pdf
    TR-4835 - https://www.netapp.com/media/19423-tr-4835.pdf
    TR-4647 - https://www.netapp.com/media/17055-tr4647.pdf
 
.EXAMPLE
 
  .\NetAppSecCheck.ps1
 
  All required values will be prompted for.
 
#>


<#PSScriptInfo
 
.VERSION 2.0
 
.GUID 5a91e6dd-0287-4a5b-860b-eed6abf74b55
 
.AUTHOR Daniel Tully
 
.RELEASENOTES
 
Version:
2.0 - Updated to make future additions more modular. Added additional output for password complexity.
Bug fixes, formatting.
 
1.2 - Added Categories for the Summary, sorted full output to match summary
Cleaned up lots of formatting issues, bug fixes, addressed ONTAP version specific issues
More bug fixes
 
1.1 - Added Summary, Full, and All output choices
Cleaned up lots of formatting issues
Reorganized Data Collection, Formatting, and Output sections
 
1.0 - Initial release
#>


#Requires -Version 7.0

$Separator = "─" * 120
$Spacer = " " * 7

# Header
$Header = @"
$Separator
  The intention of this script is to provide a quick check of several security configurations
 
  Typically the following tools provide security related information for ONTAP clusters:
    - System Manager Dashboard
    - Unified Manager cluster security objectives
    - Active IQ Digital Advisor
 
  If a more thorough review is necessary of your environment, please consider contacting
  NetApp Services to request a Data Protection and Security Assessment
 
  The documents referenced in the KB article linked below should be consulted for the most up to
  date information
 
    https://kb.netapp.com/onprem/ontap/os/How_to_perform_a_security_health_check_with_a_script_in_ONTAP
    TR-4569 - https://www.netapp.com/media/10674-tr4569.pdf
    TR-4572 - https://www.netapp.com/media/7334-tr4572.pdf
    TR-4835 - https://www.netapp.com/media/19423-tr-4835.pdf
    TR-4647 - https://www.netapp.com/media/17055-tr4647.pdf
$Separator
"@


# Gather cluster address and credentials
$NTAPCluster = Read-Host "Please enter the IP address or DNS name of the cluster to evaluate"
$Credential = Get-Credential

# Data Collection
function GetData($endpoint) {
  process {
    $Uri = "https://$NTAPCluster/api/$endpoint"
    Invoke-RestMethod -Method GET -Uri $Uri -Credential $Credential -SkipCertificateCheck
  }
}
function CollectData {
  foreach ($key in $Items.Keys) {
    if ($ChkVers -ge $Items[$key].RequiredVersion) {
      $Items[$key].Result = GetData $Items[$key].Url
      $Items[$key].Supported = $True
    } else {
      $Items[$key].Supported = $False
    }
  }
}

# Test Connection/Authentication
Try {
  $Cluster = GetData "cluster"
} Catch {
  if ($_.Exception.Message) {
    Write-Output "Error: Failed to connect or authenticate to the cluster."
    Write-Output $_.Exception.Message
  } else {
    Write-Output $_
  }
  Exit
}

# Utility stuff
$Now = Get-Date -f MM-dd-yyyy-HHmmss
$Version = $Cluster.Version.full
$VerSplit = $Version.split(":")
$ChkVers = $Cluster.Version.major + $Cluster.Version.minor / 10


# Data collection item definition
$Items = [ordered]@{
  Version = @{
    RequiredVersion = 0
    Url = "cluster"
    Category = 0
  }
  DataAtRestEncryption = @{
    RequiredVersion = 7
    Url = "security?fields=onboard_key_manager_configurable_status"
    Category = 0
  }
  NTPServers = @{
    RequiredVersion = 6
    Url = "private/cli/cluster/time-service/ntp/server"
    Category = 1
  }
  ASUPConfig = @{
    RequiredVersion = 6
    Url = "support/autosupport"
    Category = 1
  }
  ASUPTransport = @{
    RequiredVersion = 99
    Category = 1
  }
  CLITimeout = @{
    RequiredVersion = 6
    Url = "private/cli/system/timeout"
    Category = 1
  }
  CloudInsights = @{
    RequiredVersion = 10
    Url = "private/cli/cluster/agent/connection?fields=application-url"
    Category = 1
  }
  LoginBannerConfig = @{
    RequiredVersion = 6
    Url = "private/cli/security/login/banner"
    Category = 1
  }
  MOTDConfig = @{
    RequiredVersion = 6
    Url = "private/cli/security/login/motd"
    Category = 1
  }
  PasswordConfig = @{
    RequiredVersion = 6
    Url = "private/cli/security/login/role/config?fields=passwd-minlength," `
      + "passwd-min-special-chars,passwd-min-digits,passwd-min-lowercase-chars," `
      + "passwd-min-uppercase-chars,passwd-alphanum,disallowed-reuse," `
      + "passwd-expiry-time,passwd-expiry-warn-time,passwd-expiry-time," `
      + "require-initial-passwd-update,lockout-duration,change-delay"
    Category = 1
  }
  LogForwarding = @{
    RequiredVersion = 6
    Url = "private/cli/cluster/log"
    Category = 1
  }
  SystemConfigBackup = @{
    RequiredVersion = 6
    Url = "support/configuration-backup"
    Category = 1
  }
  ManagementProtocols = @{
    RequiredVersion = 6
    Url = "private/cli/security/protocol?fields=application,enabled"
    Category = 2
  }
  WebServices = @{
    RequiredVersion = 6
    Url = "private/cli/system/services/web"
    Category = 2
  }
  RSHConfig = @{
    RequiredVersion = 6
    Url = "private/cli/security/protocol?fields=application,enabled&application=rsh"
    Category = 2
  }
  TelnetConfig = @{
    RequiredVersion = 6
    Url = "private/cli/security/protocol?fields=application,enabled&application=telnet"
    Category = 2
  }
  BuiltinUsers = @{
    RequiredVersion = 6
    Url = "security/accounts?name=admin&fields=locked"
    Category = 3
  }
  DiagUser = @{
    RequiredVersion = 6
    Url = "security/accounts?name=diag&fields=locked"
    Category = 3
  }
  RestRoles = @{
    RequiredVersion = 10
    Url = "private/cli/security/login/rest-role?api=/api/storage/volumes&fields=access"
    Category = 3
  }
  UserDetails = @{
    RequiredVersion = 6
    Url = "private/cli/security/login?fields=second-authentication-method,hash-function,is-account-locked,role&application=!snmp"
    Category = 3
  }
  MultiAdminVerify = @{
    RequiredVersion = 11.1
    Url = "private/cli/security/multi-admin-verify"
    Category = 3
  }
  SNMPUsers = @{
    RequiredVersion = 6
    Url = "private/cli/security/login?application=snmp&authentication-method=community"
    Category = 3
  }
  RSHUsers = @{
    RequiredVersion = 6
    Url = "private/cli/security/login?application=rsh&fields=role,second_authentication_method,is_account_locked"
    Category = 3
  }
  TelnetUsers = @{
    RequiredVersion = 6
    Url = "private/cli/security/login?application=telnet&fields=role,second_authentication_method,is_account_locked"
    Category = 3
  }
  ClusterPeerEncryption = @{
    RequiredVersion = 6
    Url = "private/cli/cluster/peer?fields=cluster,encryption_protocol_proposed,encryption_protocol"
    Category = 4
  }
  FIPS = @{
    RequiredVersion = 6
    Url = "private/cli/security/config?fields=is_fips_enabled"
    Category = 4
  }
  IPsec = @{
    RequiredVersion = 8
    Url = "security/ipsec?fields=*"
    Category = 4
  }
  IPsecPolicy = @{
    RequiredVersion = 8
    Url = "security/ipsec/policies"
    Category = 4
  }
  SSHCiphers = @{
    RequiredVersion = 6
    Url = "private/cli/security/ssh?ciphers=*cbc*"
    Category = 4
  }
  SelfSignedCerts = @{
    RequiredVersion = 6
    Url = "private/cli/security/certificate?self_signed=true&type=server&fields=self_signed,common_name,vserver,type"
    Category = 4
  }
  SSLConfig = @{
    RequiredVersion = 6
    Url = "private/cli/security/ssl?fields=client-enabled"
    Category = 4
  }
  HTTPUsers = @{
    RequiredVersion = 6
    Url = "private/cli/security/login?application=http"
    Category = 4
  }
  ONTAPIUsers = @{
    RequiredVersion = 6
    Url = "private/cli/security/login?application=ontapi"
    Category = 4
  }
  OCSPConfig = @{
    RequiredVersion = 6
    Url = "private/cli/security/config/ocsp?fields=application,is_ocsp_enabled"
    Category = 4
  }
  SAML = @{
    RequiredVersion = 6
    Url = "private/cli/security/saml-sp/status?fields=status,is_enabled"
    Category = 4
  }
  CIFSSvms = @{
    RequiredVersion = 6
    Url = "private/cli/vserver/cifs"
    Category = 5
  }
  CIFSSigning = @{
    RequiredVersion = 6
    Url = "private/cli/vserver/cifs/security?is_signing_required=!null"
    Category = 5
  }
  CIFSWorkgroup = @{
    RequiredVersion = 6
    Url = "private/cli/vserver/cifs?auth_style=workgroup"
    Category = 5
  }
  CIFSSMB1 = @{
    RequiredVersion = 6
    Url = "private/cli/vserver/cifs/options?smb1_enabled=true"
    Category = 5
  }
  LDAP = @{
    RequiredVersion = 6
    Url = "private/cli/vserver/cifs/security?session-security-for-ad-ldap=!null"
    Category = 5
  }
  VScan = @{
    RequiredVersion = 6
    Url = "private/cli/vserver/vscan?fields=vscan-status"
    Category = 5
  }
  Fpolicy = @{
    RequiredVersion = 6
    Url = "private/cli/vserver/fpolicy?fields=status,engine"
    Category = 5
  }
  NASAuditing = @{
    RequiredVersion = 6
    Url = "private/cli/vserver/audit?fields=vserver,state"
    Category = 5
  }
  NISServers = @{
    RequiredVersion = 6
    Url = "svm/svms?nis.domain=!null"
    Category = 5
  }
  SnapShotAutoDelete = @{
    RequiredVersion = 6
    Url = "private/cli/volume/snapshot/autodelete?enabled=true"
    Category = 6
  }
  NullSnapShotPolicy = @{
    RequiredVersion = 6
    Url = "private/cli/volume?snapshot_policy=null&is_cluster_volume=true"
    Category = 6
  }
  NoneSnapShotPolicy = @{
    RequiredVersion = 6
    Url = "private/cli/volume?snapshot_policy=none&is_cluster_volume=true"
    Category = 6
  }
  SnapShotLocking = @{
    RequiredVersion = 12.1
    Url = "private/cli/volume?fields=snapshot_locking_enabled"
    Category = 6
  }
  SVMAntiRansomware = @{
    RequiredVersion = 10
    Url = "svm/svms?fields=anti_ransomware_default_volume_state"
    Category = 7
  }
  VolumeAntiRansomware = @{
    RequiredVersion = 10
    Url = "private/cli/volume?is-cluster-volume=true&fields=anti-ransomware-state"
    Category = 7
  }
  KeyManager = @{
    RequiredVersion = 6
    Url = "security/key-managers"
    Category = 8
  }
  DriveProtection = @{
    RequiredVersion = 6
    Url = "private/cli/storage/aggregate?fields=drive-protection-enabled,node"
    Category = 8
  }
  VolumeEncryption = @{
    RequiredVersion = 6
    Url = "private/cli/volume?fields=encryption-type,is-encrypted"
    Category = 8
  }
}

# Category Definition
$Categories = [ordered]@{
  0 = "Software Version"
  1 = "General Configuration"
  2 = "Administrative Protocols"
  3 = "Administrative Users"
  4 = "Secure Communication"
  5 = "File Access"
  6 = "Data Protection"
  7 = "Anti-Ransomware"
  8 = "Encryption"
}

# Process and Format Data
function ProcessData {
  ### Begin - Software Version - Category 0
  # ONTAP Version
  $Items.Version.FullHeader = @"
$Separator
Recommendation: Running a recommended release of ONTAP
Reference: SU2
https://kb.netapp.com/Support_Bulletins/Customer_Bulletins/SU2
 
"@

  $Items.Version.FullData = "$Spacer ONTAP Version: $Version`n$Spacer Data at rest encryption supported: $($Items.DataAtRestEncryption.Result.onboard_key_manager_configurable_status.supported)`n"
  $Items.Version.Summary = New-Object -TypeName psobject -Property @{
    Item = "ONTAP Version"
    Finding = $VerSplit[0]
    Topic = $Categories[$Items.Version.Category]
  }
  $Items.DataAtRestEncryption.Summary = New-Object -TypeName psobject -Property @{
    Item = "ONTAP Version Supports Encryption"
    Finding = $Items.DataAtRestEncryption.Result.onboard_key_manager_configurable_status.supported
    Topic = $Categories[$Items.DataAtRestEncryption.Category]
  }
  ### End - Software Version - Category 0

  ### Begin - General Configuration - Category 1
  # NTP Servers
  $Items.NTPServers.FullHeader = @"
$Separator
Recommendation: The number of servers configured for NTP should not be less than 3
Reference: System Manager insights and TR-4569 section "Network Time Protocol"
"@

  If ($Items.NTPServers.Result.num_records -ne 0){
    $Items.NTPServers.Formatted = ForEach ($_ in $Items.NTPServers.Result.records) {
      New-Object psobject -Property @{
        "NTP Servers" = ($_.Server).ToString()
      }
    }
    $Items.NTPServers.FullData = $Items.NTPServers.Formatted | Format-Table "NTP Servers" | Out-String -Stream | Add-Indentation
  } else {
    $Items.NTPServers.FullData = "`n$Spacer No NTP Servers Found.`n"
  }
  $Items.NTPServers.Summary = New-Object -TypeName psobject -Property @{
    Item = "3 or more NTP Servers Configured"
    Finding = ($Items.NTPServers.Result.num_records -ge 3)
    Topic = $Categories[$Items.NTPServers.Category]
  }

  # ASUP Config
  $Items.ASUPConfig.FullHeader = @"
$Separator
Recommendation: AutoSupport should use a secure protocol (HTTPS) and should be enabled
Reference: System Manager insights and TR-4569 section "NetApp AutoSupport"
"@

  $Items.ASUPConfig.Formatted = ForEach ($_ in $Items.ASUPConfig.Result) {
    New-Object psobject -Property @{
      Transport = ($_.transport).ToString()
      Enabled   = ($_.enabled).ToString()
    }
  }
  $Items.ASUPConfig.FullData = $Items.ASUPConfig.Formatted | Format-Table Transport, Enabled | Out-String -Stream | Add-Indentation
  $Items.ASUPConfig.Summary = New-Object -TypeName psobject -Property @{
    Item = "AutoSupport Enabled"
    Finding = $Items.ASUPConfig.Result.enabled
    Topic = $Categories[$Items.ASUPConfig.Category]
  }
  $Items.ASUPTransport.Summary = New-Object -TypeName psobject -Property @{
    Item = "AutoSupport Transport is HTTPS"
    Finding = ($Items.ASUPConfig.Result.transport.contains("https"))
    Topic = $Categories[$Items.ASUPTransport.Category]
  }

  # CLI Timeout
  $Items.CLITimeout.FullHeader = @"
$Separator
Recommendation: CLI timeout value should match your organization's requirements
Reference: TR-4569 section "CLI session timeout"
"@

  $Items.CLITimeout.Summary = New-Object -TypeName psobject -Property @{
    Item = "CLI Timeout Enabled"
    Finding = ($Items.CLITimeout.Result.timeout -gt 0)
    Topic = $Categories[$Items.CLITimeout.Category]
  }
  $Items.CLITimeout.FullData = "`n$Spacer CLI session timeout: $($Items.CLITimeout.Result.timeout) minutes`n"

  # Cloud Insights
  $Items.CloudInsights.FullHeader = @"
$Separator
Recommendation: Cloud Insights provides an external mode FPolicy server
Reference: TR-4572 section "Cloud Insights"
"@

  If ($Items.CloudInsights.Supported) {
    If ($Items.CloudInsights.Result.num_records -ne 0) {
      $Items.CloudInsights.FullData = $Items.CloudInsights.Result.records | Format-Table | Out-String -Stream | Add-Indentation
      $Items.CloudInsights.Summary = New-Object -TypeName psobject -Property @{
        Item = "Cloud Insights Configured"
        Finding = ($Items.CloudInsights.Result.records.application_url.contains("cloudinsights.netapp.com"))
        Topic = $Categories[$Items.CloudInsights.Category]
      }
    } Else {
      $Items.CloudInsights.FullData = "`n$Spacer No Results Returned.`n"
      $Items.CloudInsights.Summary = New-Object -TypeName psobject -Property @{
        Item = "Cloud Insights Configured"
        Finding = "False"
        Topic = $Categories[$Items.CloudInsights.Category]
      }
    }
  } Else {
    $Items.CloudInsights.Summary = New-Object -TypeName psobject -Property @{
      Item = "Cloud Insights Configured"
      Finding = "Not available in this release"
      Topic = $Categories[$Items.CloudInsights.Category]
    }
    $Items.CloudInsights.FullData = "`n$Spacer Not available in this release.`n"
  }

  # Banner and MOTD
  $Items.LoginBannerConfig.FullHeader = @"
$Separator
Recommendation: The login banner and message of the day (motd) should match your organization's requirements
Reference: System Manager insights and TR-4569 section "Login banners" and "Message of the day"
"@

  $Items.LoginBannerConfig.Summary = New-Object -TypeName psobject -Property @{
    Item = "Login Banner Configured"
    Finding = ($Items.LoginBannerConfig.Result.num_records -ne 0)
    Topic = $Categories[$Items.LoginBannerConfig.Category]
  }
  $Items.MOTDConfig.Summary = New-Object -TypeName psobject -Property @{
    Item = "MOTD Configured"
    Finding = ($Items.MOTDConfig.Result.num_records -ne 0)
    Topic = $Categories[$Items.MOTDConfig.Category]
  }
  $Items.LoginBannerConfig.FullData = "`n$Spacer Login banner configured - $($Items.LoginBannerConfig.Result.num_records -ne 0)"
  $Items.MOTDConfig.FullData = "`n$Spacer MOTD configured - $($Items.MOTDConfig.Result.num_records -ne 0)`n"

  # Password Complexity
  $Items.PasswordConfig.FullHeader = @"
$Separator
Recommendation: Configured password parameters should match your organization's policy
Reference: TR-4569 section "Password parameters"
"@

  $Items.PasswordConfig.Formatted = ForEach ($_ in $Items.PasswordConfig.Result.records) {
    New-Object psobject -Property @{
      VServer             = ($_.vserver).ToString()
      Role                = ($_.role).ToString()
      Alphanumeric        = ($_.passwd_alphanum).ToString()
      "Min Len"           = ($_.passwd_minlength).ToString()
      "Min Spec Chars"    = ($_.passwd_min_special_chars).ToString()
      "Min Lowercase"     = ($_.passwd_min_lowercase_chars).ToString()
      "Min Uppercase"     = ($_.passwd_min_uppercase_chars).ToString()
      "Min Digits"        = ($_.passwd_min_digits).ToString()
      "Before Reuse"      = ($_.disallowed_reuse).ToString()
      "Expiry Time"       = ($_.passwd_expiry_time).ToString()
      "Expiry Warn"       = ($_.passwd_expiry_warn_time).ToString()
      "Age Before Chg"    = ($_.change_delay).ToString()
      "ChgOnLogin"        = ($_.require_initial_passwd_update).ToString()
      "Lockout Duration"  = ($_.lockout_duration).ToString()
    }
  }
  $Items.PasswordConfig.Summary = New-Object -TypeName psobject -Property @{
    Item = "Password Complexity Configuration"
    Finding = "Review Full Output"
    Topic = $Categories[$Items.PasswordConfig.Category]
  }
  $Items.PasswordConfig.FullData = $Items.PasswordConfig.Formatted | Format-Table vserver, role, Alphanumeric, "Min Len", "Min Spec Chars", "Min Lowercase", "Min Uppercase", "Min Digits" | Out-String -Stream | Add-Indentation
  $Items.PasswordConfig.FullData += $Items.PasswordConfig.Formatted | Format-Table vserver, role, "Before Reuse", "Expiry Time", "Expiry Warn", "Age Before Chg", "ChgOnLogin", "Lockout Duration" | Out-String -Stream | Add-Indentation

  # Log Forwarding
  $Items.LogForwarding.FullHeader = @"
$Separator
Recommendation: Offloading of syslog information should be configured
Reference: TR-4569 section "Sending out syslog"
"@

  If ($Items.LogForwarding.Result.num_records -ne 0) {
    $Items.LogForwarding.Formatted = ForEach ($_ in $Items.LogForwarding.Result.records) {
      New-Object psobject -Property @{
        Destination = ($_.Destination).ToString()
        Port        = ($_.Port).ToString()
      }
    }
    $Items.LogForwarding.FullData = $Items.LogForwarding.Formatted | Format-Table Destination, Port | Out-String -Stream | Add-Indentation
  } else {
    $Items.LogForwarding.FullData = "`n$Spacer No Results Returned.`n"
  }
  $Items.LogForwarding.Summary = New-Object -TypeName psobject -Property @{
    Item = "Syslog Forwarding Configured"
    Finding = ($Items.LogForwarding.Result.num_records -ne 0)
    Topic = $Categories[$Items.LogForwarding.Category]
  }

  # System Configuration Backup
  $Items.SystemConfigBackup.FullHeader = @"
$Separator
Recommendation: System Configuration should be backed up to a remote server
"@

  If ($Items.SystemConfigBackup.Result.url) {
    $Items.SystemConfigBackup.FullData = "System Configuration Backup Destination:`n$Spacer $($Items.SystemConfigBackup.Result.url)`n"
  } Else {
    $Items.SystemConfigBackup.FullData = "`n$Spacer No Remote Destination Found.`n"
  }
  $Items.SystemConfigBackup.Summary = New-Object -TypeName psobject -Property @{
    Item = "System Configuration Backup to Remote Server"
    Finding = (![string]::IsNullOrEmpty($Items.SystemConfigBackup.Result.url))
    Topic = $Categories[$Items.SystemConfigBackup.Category]
  }
  ### End - General Configuration - Category 1

  ### Begin - Administrative Protocols - Category 2
  # Web Services
  $Items.WebServices.FullHeader = @"
$Separator
Recommendation: HTTP should be disabled
"@

  $Items.WebServices.FullData = "`n$Spacer HTTP Enabled - $($Items.WebServices.Result.http_enabled)`n"
  $Items.WebServices.Summary = New-Object -TypeName psobject -Property @{
    Item = "HTTP Disabled"
    Finding = (!$Items.WebServices.Result.http_enabled)
    Topic = $Categories[$Items.WebServices.Category]
  }

  # Management Protocols
  $Items.ManagementProtocols.FullHeader = @"
$Separator
Recommendation: Telnet and Remote Shell (RSH) should be disabled
Reference: System Manager insights and TR-4569 section "Application methods"
"@

  $Items.ManagementProtocols.Formatted = ForEach ($_ in $Items.ManagementProtocols.Result.records) {
    New-Object psobject -Property @{
      Application = ($_.application).ToString()
      Enabled     = ($_.enabled).ToString()
    }
  }
  $Items.ManagementProtocols.FullData = $Items.ManagementProtocols.Formatted | Format-Table Application, Enabled | Out-String -Stream | Add-Indentation
  $Items.RSHConfig.Summary = New-Object -TypeName psobject -Property @{
    Item = "RSH Disabled"
    Finding = (!$Items.RSHConfig.Result.records.enabled)
    Topic = $Categories[$Items.RSHConfig.Category]
  }
  $Items.TelnetConfig.Summary = New-Object -TypeName psobject -Property @{
    Item = "Telnet Disabled"
    Finding = (!$Items.TelnetConfig.Result.records.enabled)
    Topic = $Categories[$Items.TelnetConfig.Category]
  }
  ### End - Administrative Protocols - Category 2

  ### Begin - Administrative Users - Category 3
  # Built in accounts
  $Items.BuiltinUsers.FullHeader = @"
$Separator
Recommendation: Built in accounts should be locked
Reference: System Manager insights and TR-4569 section "Default administrative accounts"
"@

  $Items.BuiltinUsers.Builtin = $Items.BuiltinUsers.Result.records + $Items.DiagUser.Result.records
  $Items.BuiltinUsers.Formatted = ForEach ($_ in $Items.BuiltinUsers.Builtin) {
    New-Object psobject -Property @{
      Username = ($_.name).ToString()
      Locked   = ($_.locked).ToString()
    }
  }
  $Items.BuiltinUsers.FullData = $Items.BuiltinUsers.Formatted | Format-Table Username, Locked | Out-String -Stream | Add-Indentation
  $Items.BuiltinUsers.Summary = New-Object -TypeName psobject -Property @{
    Item = "Default Administrative Accounts Locked"
    Finding = (!$Items.BuiltinUsers.Formatted.Locked.Contains("False"))
    Topic = $Categories[$Items.BuiltinUsers.Category]
  }

  # REST Roles
  $Items.RestRoles.FullHeader = @"
$Separator
Recommendation: You can prevent ONTAP administrators from using REST APIs for file access by setting access level
                for /api/storage/volumes to none
Reference: TR-4569 section "Effect of REST APIs on NAS auditing"
"@

  If ($Items.RestRoles.Supported) {
    $Items.RestRoles.Formatted = ForEach ($_ in $Items.RestRoles.Result.records) {
      New-Object psobject -Property @{
        VServer = ($_.vserver).ToString()
        Role    = ($_.role).ToString()
        API     = ($_.api).ToString()
        Access  = ($_.Access).ToString()
      }
    }
    $Items.RestRoles.FullData = $Items.RestRoles.Formatted | Format-Table VServer, Role, API, Access | Out-String -Stream | Add-Indentation
  } else {
    $Items.RestRoles.FullData = "`n$Spacer Not Supported in this release.`n"
  }

  # User Details
  $Items.UserDetails.FullHeader = @"
$Separator
Recommendation: For each login the authentication-method should be public key for machine access
                and can be password for user access
                The second-authentication-method should not be none to enable MFA
                The role should be appropriate to grant them privilege to perform their job function or required task
                The hash-function should be sha512
Reference: TR-4569 section "SHA-512 support" and "Managing SSHv2" and "Roles, applications, and authentication" and
           TR-4647 section "ONTAP SSH two-factor chained authentication"
"@

  $Items.UserDetails.Formatted = ForEach ($_ in $Items.UserDetails.Result.records) {
    New-Object psobject -Property @{
      VServer         = ($_.vserver).ToString()
      Username        = ($_.user_or_group_name).ToString()
      Application     = ($_.application).ToString()
      AuthMethod      = ($_.authentication_method).ToString()
      "Role Name"     = ($_.role).ToString()
      Locked          = ($_.is_account_locked).ToString()
      "2ndAuthMethod" = ($_.second_authentication_method).ToString()
      "Hash Function" = ($_.hash_function).ToString()
    }
  }
  $Items.UserDetails.FullData = $Items.UserDetails.Formatted | Format-Table username, vserver, application, "role name", authmethod, "2ndAuthMethod", Locked, "Hash Function" | Out-String -Stream | Add-Indentation

  # Multi-Admin Verification
  $Items.MultiAdminVerify.FullHeader = @"
$Separator
Recommendation: Multi-admin verification should be enabled
Reference: TR-4569 section "Multi-admin verification"
"@

  If ($Items.MultiAdminVerify.Supported) {
    $Items.MultiAdminVerify.Formatted = New-Object psobject -Property @{
      Enabled              = ($Items.MultiAdminVerify.Result.enabled).ToString()
      "Required Approvers" = ($Items.MultiAdminVerify.Result.required_approvers).ToString()
    }
    $Items.MultiAdminVerify.Summary = New-Object -TypeName psobject -Property @{
      Item = "Multi-Admin-Verify Configured"
      Finding = $Items.MultiAdminVerify.Result.enabled
      Topic = $Categories[$Items.MultiAdminVerify.Category]
    }
    $Items.MultiAdminVerify.FullData = $Items.MultiAdminVerify.Formatted | Format-Table Enabled, "Required Approvers" | Out-String -Stream | Add-Indentation
  } else {
    $Items.MultiAdminVerify.Summary = New-Object -TypeName psobject -Property @{
      Item = "Multi-Admin-Verify Configured"
      Finding = "Not available in this release"
      Topic = $Categories[$Items.MultiAdminVerify.Category]
    }
    $Items.MultiAdminVerify.FullData = "`n$Spacer Multi-admin-verify is not supported on this release. Consider upgrading to 9.11.1 or later.`n"
  }

  # SNMP Users
  $Items.SNMPUsers.FullHeader = @"
$Separator
Recommendation: SNMP Users shoud not use an authentication method of community
"@

  If ($Items.SNMPUsers.Result.num_records -ne 0) {
    $Items.SNMPUsers.Formatted = ForEach ($_ in $Items.SNMPUsers.Result.records) {
      New-Object psobject -Property @{
        VServer     = ($_.vserver).ToString()
        Username    = ($_.user_or_group_name).ToString()
        Application = ($_.application).ToString()
        AuthMethod  = ($_.authentication_method).ToString()
      }
    }
    $Items.SNMPUsers.FullData = $Items.SNMPUserss.Formatted | Format-Table username, vserver, application, authmethod | Out-String -Stream | Add-Indentation
  } Else {
    $Items.SNMPUsers.FullData = "`n$Spacer No SNMP users found with community authentication method.`n"
  }
  $Items.SNMPUsers.Summary = New-Object -TypeName psobject -Property @{
    Item = "No SNMP Users with Auth Method of Community"
    Finding = ($Items.SNMPUsers.Result.num_records -eq 0)
    Topic = $Categories[$Items.SNMPUsers.Category]
  }

  # RSH and Telnet Users
  $Items.RSHUsers.FullHeader = @"
$Separator
Recommendation: No logins should exist with the Telnet or RSH application
Reference: TR-4569 section "Application methods"
"@

  If ($Items.RSHUsers.Result.num_records -ne 0) {
    $Items.RSHUsers.Formatted = ForEach ($_ in $Items.RSHUsers.Result.records) {
      New-Object psobject -Property @{
        VServer         = ($_.vserver).ToString()
        Username        = ($_.user_or_group_name).ToString()
        Application     = ($_.application).ToString()
        AuthMethod      = ($_.authentication_method).ToString()
        "Role Name"     = ($_.role).ToString()
        Locked          = ($_.is_account_locked).ToString()
        "2ndAuthMethod" = ($_.second_authentication_method).ToString()
      }
    }
    $Items.RSHUsers.FullData = $Items.RSHUsers.Formatted | Format-Table username, vserver, application, "role name", authmethod, "2ndAuthMethod", Locked | Out-String -Stream | Add-Indentation
  } else {
    $Items.RSHUsers.FullData = "`n$Spacer No users found with RSH application access."
  }
  $Items.RSHUsers.Summary = New-Object -TypeName psobject -Property @{
    Item = "No Users Present with RSH Access"
    Finding = ($Items.RSHUsers.Result.num_records -eq 0)
    Topic = $Categories[$Items.RSHUsers.Category]
  }
  If ($Items.TelnetUsers.Result.num_records -ne 0) {
    $Items.TelnetUsers.Formatted = ForEach ($_ in $Items.TelnetUsers.Result.records) {
      New-Object psobject -Property @{
        VServer         = ($_.vserver).ToString()
        Username        = ($_.user_or_group_name).ToString()
        Application     = ($_.application).ToString()
        AuthMethod      = ($_.authentication_method).ToString()
        "Role Name"     = ($_.role).ToString()
        Locked          = ($_.is_account_locked).ToString()
        "2ndAuthMethod" = ($_.second_authentication_method).ToString()
      }
    }
    $Items.TelnetUsers.FullData = $Items.TelnetUsers.Formatted | Format-Table username, vserver, application, "role name", authmethod, "2ndAuthMethod", Locked | Out-String -Stream | Add-Indentation
  } else {
    $Items.TelnetUsers.FullData = "`n$Spacer No users found with Telnet application access.`n"
  }
  $Items.TelnetUsers.Summary = New-Object -TypeName psobject -Property @{
    Item = "No Users Present with Telnet Access"
    Finding = ($Items.TelnetUsers.Result.num_records -eq 0)
    Topic = $Categories[$Items.TelnetUsers.Category]
  }
  ### End - Administrative Users - Category 3

  ### Begin - Secure Communication - Category 4
  # Cluster Peer Details
  $Items.ClusterPeerEncryption.FullHeader = @"
$Separator
Recommendation: Cluster peers should be configured with tls-psk for the encryption protocol
Reference: TR-4569 section "Data replication encryption"
"@

  If ($Items.ClusterPeerEncryption.Result.num_records -ne 0) {
    $Items.ClusterPeerEncryption.Formatted = ForEach ($_ in $Items.ClusterPeerEncryption.Result.records) {
      New-Object psobject -Property @{
        Cluster               = ($_.cluster).ToString()
        "Encryption Protocol" = ($_.encryption_protocol).ToString()
      }
    }
    $Items.ClusterPeerEncryption.FullData = $Items.ClusterPeerEncryption.Formatted | Format-Table Cluster, "Encryption Protocol" | Out-String -Stream | Add-Indentation
    $Items.ClusterPeerEncryption.Summary = New-Object -TypeName psobject -Property @{
      Item = "Encryption Enabled for all Cluster Peers"
      Finding = (!$Items.ClusterPeerEncryption.Formatted."Encryption Protocol".contains("none"))
      Topic = $Categories[$Items.ClusterPeerEncryption.Category]
    }
  } else {
    $Items.ClusterPeerEncryption.FullData = "`n$Spacer No Cluster Peer Relationships Found.`n"
    $Items.ClusterPeerEncryption.Summary = New-Object -TypeName psobject -Property @{
      Item = "Encryption Enabled for all Cluster Peers"
      Finding = "No Cluster Peers Found"
      Topic = $Categories[$Items.ClusterPeerEncryption.Category]
    }
  }

  # FIPS
  $Items.FIPS.FullHeader = @"
$Separator
Recommendation: FIPS Mode should be enabled
Reference: System Manager insights and TR-4569 section "Managing TLS and SSL"
"@

  $Items.FIPS.Formatted = ForEach ($_ in $Items.FIPS.Result.records) {
    New-Object psobject -Property @{
      Interface      = ($_.interface).ToString()
      "FIPS Enabled" = ($_.is_fips_enabled).ToString()
    }
  }
  $Items.FIPS.FullData = $Items.FIPS.Formatted | Format-Table Interface, "FIPS Enabled" | Out-String -Stream | Add-Indentation
  $Items.FIPS.Summary = New-Object -TypeName psobject -Property @{
    Item = "FIPS Mode Enabled"
    Finding = $Items.FIPS.Result.records.is_fips_enabled
    Topic = $Categories[$Items.FIPS.Category]
  }

  # IPSec
  $Items.IPSec.FullHeader = @"
$Separator
Recommendation: When required IPsec is configured and policies created
Reference: TR-4569 section "IPsec data-in-flight encryption"
"@

  If ($Items.IPSec.Supported) {
    $Items.IPSec.FullData = "`n$Spacer IPsec Enabled - $($Items.IPsec.Result.enabled)`n"
    If ($Items.IPsecPol.Result.num_records -ne 0) {
      $Items.IPSecPolicy.FullData = $Items.IPsecPol.Result.records | Format-Table | Out-String -Stream | Add-Indentation
    } Else {
      $Items.IPSecPolicy.FullData = "$Spacer No IPsec Policies Found.`n"
    }
    $Items.IPSec.Summary = New-Object -TypeName psobject -Property @{
      Item = "IPsec Enabled"
      Finding = $Items.IPsec.Result.enabled
      Topic = $Categories[$Items.IPSec.Category]
    }
    $Items.IPSecPolicy.Summary = New-Object -TypeName psobject -Property @{
      Item = "IPsec Policies Configured"
      Finding = ($Items.IPsecPol.Result.num_records -ne 0)
      Topic = $Categories[$Items.IPSecPolicy.Category]
    }
  } Else {
    $Items.IPSec.FullData = "`n$Spacer IPsec is not supported on this release. Consider upgrading to 9.8 or later.`n"
    $Items.IPSecPolicy.FullData = "$Spacer IPsec is not supported on this release. Consider upgrading to 9.8 or later.`n"
    $Items.IPSec.Summary = New-Object -TypeName psobject -Property @{
      Item = "IPsec Enabled"
      Finding = "Not available in this release"
      Topic = $Categories[$Items.IPSec.Category]
    }
    $Items.IPSecPolicy.Summary = New-Object -TypeName psobject -Property @{
      Item = "IPsec Policies Configured"
      Finding = "Not available in this release"
      Topic = $Categories[$Items.IPSecPolicy.Category]
    }
  }

  # Problematic Ciphers
  $Items.SSHCiphers.FullHeader = @"
$Separator
Recommendation: No ciphers should exist that have names containing "cbc"
Reference: System Manager insights
 
$Spacer Problematic Ciphers:
"@

  If ($Items.SSHCiphers.Result.num_records -ne 0) {
    ForEach ($_ in $Items.SSHCiphers.Result.records) {
      $Items.SSHCiphers.VServer = $_.vserver
      $Items.SSHCiphers.Ciphers = $_.ciphers -split ","
      $Items.SSHCiphers.Formatted = @()
      ForEach ($_ in $Items.SSHCiphers.Ciphers) {
        If ($_.contains("cbc")) {
          $Items.SSHCiphers.Formatted += New-Object -TypeName psobject -Property @{VServer = $Items.SSHCiphers.VServer; Cipher = $_ }
        }
      }
    }
    $Items.SSHCiphers.FullData = $Items.SSHCiphers.Formatted | Format-Table VServer, Cipher | Out-String -Stream | Add-Indentation
  } else {
    $Items.SSHCiphers.FullData = "`n$Spacer No Problematic Ciphers Found.`n"
  }
  $Items.SSHCiphers.Summary = New-Object -TypeName psobject -Property @{
    Item = "No Problematic Ciphers Present"
    Finding = ($Items.SSHCiphers.Result.num_records -eq 0)
    Topic = $Categories[$Items.SSHCiphers.Category]
  }

  # Self-Signed Certificates
  $Items.SelfSignedCerts.FullHeader = @"
$Separator
Recommendation: On production systems no self-signed ceritficates should exist
Reference: TR-4569 section "Creating a CA-signed digital certificate"
"@

  If ($Items.SelfSignedCerts.Result.num_records -ne 0) {
    $Items.SelfSignedCerts.Formatted = ForEach ($_ in $Items.SelfSignedCerts.Result.records) {
      New-Object psobject -Property @{
        VServer       = ($_.vserver).ToString()
        CommonName    = ($_.common_name).ToString()
        Serial        = ($_.serial).ToString()
        CA            = ($_.ca).ToString()
        Type          = ($_.type).ToString()
        "Self-Signed" = ($_.self_signed).ToString()
      }
    }
    $Items.SelfSignedCerts.FullData = $Items.SelfSignedCerts.Formatted | Format-Table Vserver, CommonName, Serial, CA, Type, "Self-Signed" | Out-String -Stream | Add-Indentation
  } else {
    $Items.SelfSignedCerts.FullData = "`n$Spacer No Results Returned.`n"
  }
  $Items.SelfSignedCerts.Summary = New-Object -TypeName psobject -Property @{
    Item = "No Self-signed Certificates Present"
    Finding = ($Items.SelfSignedCerts.Result.num_records -eq 0)
    Topic = $Categories[$Items.SelfSignedCerts.Category]
  }

  # SSL Client, HTTP, and ONTAPI Users
  $Items.SSLConfig.FullHeader = @"
$Separator
Recommendation: For any SVM with client-enabled access, all related logins that are performing SDK or REST API calls
                should use cert for Authentication Method field
Reference: TR-4569 section "Certificate-based API access"
 
$Spacer SSL Configuration
"@

  $Items.SSLConfig.Formatted = ForEach ($_ in $Items.SSLConfig.Result.records) {
    New-Object psobject -Property @{
      VServer          = ($_.vserver).ToString()
      "Client Enabled" = ($_.client_enabled).ToString()
    }
  }
  $Items.SSLConfig.FullData = $Items.SSLConfig.Formatted | Format-Table VServer, "Client Enabled" | Out-String -Stream | Add-Indentation
  $Items.SSLConfig.Summary = New-Object -TypeName psobject -Property @{
    Item = "No SVMs with client-enabled SSL Access Present"
    Finding = (!$Items.SSLConfig.Formatted."Client Enabled".contains("True)"))
    Topic = $Categories[$Items.SSLConfig.Category]
  }
  $Items.HTTPUsers.Formatted = ForEach ($_ in $Items.HTTPUsers.Result.records) {
    New-Object psobject -Property @{
      VServer     = ($_.vserver).ToString()
      Username    = ($_.user_or_group_name).ToString()
      Application = ($_.application).ToString()
      AuthMethod  = ($_.authentication_method).ToString()
    }
  }
  $Items.ONTAPIUsers.Formatted = ForEach ($_ in $Items.ONTAPIUsers.Result.records) {
    New-Object psobject -Property @{
      VServer     = ($_.vserver).ToString()
      Username    = ($_.user_or_group_name).ToString()
      Application = ($_.application).ToString()
      AuthMethod  = ($_.authentication_method).ToString()
    }
  }
  If ($Items.HTTPUsers.Result.records.num_records -ne 0) {
    $Items.HTTPUsers.FullHeader = "$Spacer HTTP Users"
    $Items.HTTPUsers.FullData = $Items.HTTPUsers.Formatted | Format-Table VServer, Username, Application, AuthMethod | Out-String -Stream | Add-Indentation
  } Else {
    $Items.HTTPUsers.FullData = "`n$Spacer No HTTP Users Found.`n"
  }
  If ($Items.ONTAPIUsers.Result.records.num_records -ne 0) {
    $Items.ONTAPIUsers.FullHeader = "$Spacer ONTAPI Users"
    $Items.ONTAPIUsers.FullData = $Items.ONTAPIUsers.Formatted | Format-Table VServer, Username, Application, AuthMethod | Out-String -Stream | Add-Indentation
  } Else {
    $Items.ONTAPIUsers.FullData = "`n$Spacer No ONTAPI Users Found.`n"
  }

  # OCSP
  $Items.OCSPConfig.FullHeader = @"
$Separator
Recommendation: OCSP should be enabled
Reference: TR-4569 section "Online certificate status protocol"
"@

  $Items.OCSPConfig.Formatted = ForEach ($_ in $Items.OCSPConfig.Result.records) {
    New-Object psobject -Property @{
      Application    = ($_.application).ToString()
      "OCSP Enabled" = ($_.is_ocsp_enabled).ToString()
    }
  }
  $Items.OCSPConfig.FullData = $Items.OCSPConfig.Formatted | Format-Table Application, "OCSP Enabled" | Out-String -Stream | Add-Indentation
  $Items.OCSPConfig.Summary = New-Object -TypeName psobject -Property @{
    Item = "OCSP Enabled for all Applications"
    Finding = (!$Items.OCSPConfig.Formatted."OCSP Enabled".contains("False"))
    Topic = $Categories[$Items.OCSPConfig.Category]
  }

  # SAML
  $Items.SAML.FullHeader = @"
$Separator
Recommendation: SAML should be configured
Reference: TR-4647 section "The requirement for strong administrative credentials"
"@

  If ($Items.SAML.Result.records.num_records -ne 0) {
    $Items.SAML.Formatted = ForEach ($_ in $Items.SAML.Result.records) {
      New-Object psobject -Property @{
        Node    = ($_.node).ToString()
        Status  = ($_.status).ToString()
        Enabled = ($_.is_enabled).ToString()
      }
    }
    $Items.SAML.FullData = $Items.SAML.Formatted | Format-Table Node, Status, Enabled | Out-String -Stream | Add-Indentation
  } else {
    $Items.SAML.FullData = "`n$Spacer No Results Returned.`n"
  }
  $Items.SAML.Summary = New-Object -TypeName psobject -Property @{
    Item = "SAML Configured"
    Finding = (!$Items.SAML.Formatted.enabled.contains("False"))
    Topic = $Categories[$Items.SAML.Category]
  }
  ### End - Secure Communication - Category 4

  ### Begin - File Access - Category 5
  ## CIFS Things
  If ($Items.CIFSSvms.Result.num_records -ne 0) {
    # CIFS Signing
    If ($Items.CIFSSigning.Result.num_records -ne 0) {
      $Items.CIFSSigning.Formatted = ForEach ($_ in $Items.CIFSSigning.Result.records) {
        New-Object psobject -Property @{
          VServer            = ($_.vserver).ToString()
          "Signing Required" = ($_.is_signing_required).ToString()
        }
      }
      $Items.CIFSSigning.FullHeader = @"
$Separator
Recommendation: For each SVM configured with CIFS the is-signing-required should be true
Reference: TR-4569 section "CIFS SMB signing and sealing"
"@

      $Items.CIFSSigning.FullData = $Items.CIFSSigning.Formatted | Format-Table VServer, "Signing Required" | Out-String -Stream | Add-Indentation
      $Items.CIFSSigning.Summary = New-Object -TypeName psobject -Property @{
        Item = "All CIFS SVMs have Signing Enabled"
        Finding = (!$Items.CIFSSigning.Formatted."Signing Required".contains("False"))
        Topic = $Categories[$Items.CIFSSigning.Category]
      }
    } Else {
      $Items.CIFSSigning.FullHeader = @"
$Separator
Recommendation: For each SVM configured with CIFS the is-signing-required should be true
Reference: TR-4569 section "CIFS SMB signing and sealing"
"@

      $Items.CIFSSigning.FullData = "`n$Spacer No CIFS SVMs configured for required signing.`n"
      $Items.CIFSSigning.Summary = New-Object -TypeName psobject -Property @{
        Item = "All CIFS SVMs have Signing Enabled"
        Finding = (!$Items.CIFSSigning.Formatted."Signing Required".contains("False"))
        Topic = $Categories[$Items.CIFSSigning.Category]
      }
    }
    # CIFS Workgroups
    If ($Items.CIFSWorkgroup.Result.num_records -ne 0) {
      $Items.CIFSWorkgroup.Formatted = ForEach ($_ in $Items.CIFSWorkgroup.Result.records) {
        New-Object psobject -Property @{
          VServer      = ($_.vserver).ToString()
          "Auth Style" = ($_.auth_style).ToString()
        }
      }
      $Items.CIFSWorkgroup.FullHeader = @"
$Separator
Recommendation: CIFS SVMs should not be configured for workgroup access
"@

      $Items.CIFSWorkgroup.FullData = $Items.CIFSWorkgroup.Formatted | Format-Table VServer, "Auth Style" | Out-String -Stream | Add-Indentation
      $Items.CIFSWorkgroup.Summary = New-Object -TypeName psobject -Property @{
        Item = "No CIFS SVMs are configured for Workgroup"
        Finding = ($Items.CIFSWorkgroup.Result.num_records -eq 0)
        Topic = $Categories[$Items.CIFSWorkgroup.Category]
      }
    } Else{
      $Items.CIFSWorkgroup.FullHeader = @"
$Separator
Recommendation: CIFS SVMs should not be configured for workgroup access
"@

      $Items.CIFSWorkgroup.FullData = "`n$Spacer No CIFS SVMs configured for workgroup access.`n"
      $Items.CIFSWorkgroup.Summary = New-Object -TypeName psobject -Property @{
        Item = "No CIFS SVMs are configured for Workgroup"
        Finding = ($Items.CIFSWorkgroup.Result.num_records -eq 0)
        Topic = $Categories[$Items.CIFSWorkgroup.Category]
      }
    }
    # CIFS SMB1
    If ($Items.CIFSSMB1.Result.num_records -ne 0) {
      $Items.CIFSSMB1.Formatted = ForEach ($_ in $Items.CIFSSMB1.Result.records) {
        New-Object psobject -Property @{
          VServer        = ($_.vserver).ToString()
          "SMB1 Enabled" = ($_.smb1_enabled).ToString()
        }
      }
      $Items.CIFSSMB1.FullHeader = @"
$Separator
Recommendation: CIFS SVMs should not be configured to use SMB1
"@

      $Items.CIFSSMB1.FullData = $Items.CIFSSMB1.Formatted | Format-Table VServer, "SMB1 Enabled" | Out-String -Stream | Add-Indentation
      $Items.CIFSSMB1.Summary = New-Object -TypeName psobject -Property @{
        Item = "No CIFS SVMs have SMB 1 Enabled"
        Finding = ($Items.CIFSSMB1.Result.num_records -eq 0)
        Topic = $Categories[$Items.CIFSSMB1.Category]
      }
    } Else {
      $Items.CIFSSMB1.FullHeader = @"
$Separator
Recommendation: CIFS SVMs should not be configured to use SMB1
"@

      $Items.CIFSSMB1.FullData = "`n$Spacer No CIFS SVMs configured for SMB1.`n"
      $Items.CIFSSMB1.Summary = New-Object -TypeName psobject -Property @{
        Item = "No CIFS SVMs have SMB 1 Enabled"
        Finding = ($Items.CIFSSMB1.Result.num_records -eq 0)
        Topic = $Categories[$Items.CIFSSMB1.Category]
      }
    }
    If ($Items.LDAP.Result.num_records -ne 0) {
      $Items.LDAP.Formatted = ForEach ($_ in $Items.LDAP.Result.records) {
        If ($Items.CIFSSvms.Result.records -match ($_.vserver)) {
          New-Object psobject -Property @{
            VServer                        = ($_.vserver).ToString()
            "Session Security for AD LDAP" = ($_.session_security_for_ad_ldap).ToString()
          }
        }
      }
      $Items.LDAP.FullHeader = @"
$Separator
Recommendation: For each SVM configured with CIFS session-security-for-ad-ldap should be set to a minimum of sign
                to match your organization's requirements
Reference: TR-4835 section "Microsoft LDAP channel binding requirement"
"@

      $Items.LDAP.FullData = $Items.LDAP.Formatted | Format-Table VServer, "Session Security for AD LDAP" | Out-String -Stream | Add-Indentation
      $Items.LDAP.Summary = New-Object -TypeName psobject -Property @{
        Item = "AD LDAP Session Security Enabled for all CIFS SVMs"
        Finding = (!$Items.LDAP.Formatted."Session Security for AD LDAP".contains("none"))
        Topic = $Categories[$Items.LDAP.Category]
      }
    } Else{
      $Items.LDAP.FullHeader = @"
$Separator
Recommendation: For each SVM configured with CIFS session-security-for-ad-ldap should be set to a minimum of sign
                to match your organization's requirements
Reference: TR-4835 section "Microsoft LDAP channel binding requirement"
"@

      $Items.LDAP.FullData = "`n$Spacer No CIFS SVMs Configured for LDAP Found.`n"
      $Items.LDAP.Summary = New-Object -TypeName psobject -Property @{
        Item = "AD LDAP Session Security Enabled for all CIFS SVMs"
        Finding = (!$Items.LDAP.Formatted."Session Security for AD LDAP".contains("none"))
        Topic = $Categories[$Items.LDAP.Category]
      }
    }
    # VScan
    If ($Items.VScan.Result.num_records -ne 0) {
      $Items.VScan.Formatted = ForEach ($_ in $Items.VScan.Result.records) {
        If ($Items.CIFSSvms.Result.records -match ($_.vserver)) {
          New-Object psobject -Property @{
            VServer        = ($_.vserver).ToString()
            "VScan Status" = ($_.vscan_status).ToString()
          }
        }
      }
      $Items.VScan.FullHeader = @"
$Separator
Recommendation: VScan can be configured for CIFS SVMs
"@

      $Items.VScan.FullData = $Items.VScan.Formatted | Format-Table VServer, "VScan Status" | Out-String -Stream | Add-Indentation
      $Items.VScan.Summary = New-Object -TypeName psobject -Property @{
        Item = "VScan Enabled for all CIFS SVMs"
        Finding = (!$Items.VScan.Formatted."VScan Status".contains("off"))
        Topic = $Categories[$Items.VScan.Category]
      }
    } Else {
      $Items.VScan.FullHeader = @"
$Separator
Recommendation: VScan can be configured for CIFS SVMs
"@

      $Items.VScan.FullData = "`n$Spacer VScan not configured for any CIFS SVMs."
      $Items.VScan.Summary = New-Object -TypeName psobject -Property @{
        Item = "VScan Enabled for all CIFS SVMs"
        Finding = (!$Items.VScan.Formatted."VScan Status".contains("off"))
        Topic = $Categories[$Items.VScan.Category]
      }
    }
  } Else {
    $Items.CIFSSigning.Summary = New-Object -TypeName psobject -Property @{
      Item = "All CIFS SVMs have Signing Enabled"
      Finding = "No CIFS SVMs Found"
      Topic = $Categories[$Items.CIFSSigning.Category]
    }
    $Items.CIFSWorkgroup.Summary = New-Object -TypeName psobject -Property @{
      Item = "No CIFS SVMs are configured for Workgroup"
      Finding = "No CIFS SVMs Found"
      Topic = $Categories[$Items.CIFSWorkgroup.Category]
    }
    $Items.CIFSSMB1.Summary = New-Object -TypeName psobject -Property @{
      Item = "No CIFS SVMs have SMB 1 Enabled"
      Finding = "No CIFS SVMs Found"
      Topic = $Categories[$Items.CIFSSMB1.Category]
    }
    $Items.LDAP.Summary = New-Object -TypeName psobject -Property @{
      Item = "AD LDAP Session Security Enabled for all CIFS SVMs"
      Finding = "No CIFS SVMs Found"
      Topic = $Categories[$Items.LDAP.Category]
    }
    $Items.VScan.Summary = New-Object -TypeName psobject -Property @{
      Item = "VScan Enabled for all CIFS SVMs"
      Finding = "No CIFS SVMs Found"
      Topic = $Categories[$Items.VScan.Category]
    }
  }

  # FPolicy
  $Items.FPolicy.FullHeader = @"
$Separator
Recommendation: FPolicy should be configured
Reference: System Manager insights
"@

  If ($Items.Fpolicy.Result.num_records -ne 0) {
    $Items.Fpolicy.Formatted = ForEach ($_ in $Items.Fpolicy.Result.records) {
      New-Object psobject -Property @{
        VServer       = ($_.vserver).ToString()
        "Policy Name" = ($_.policy_name).ToString()
        Status        = ($_.status).ToString()
        Engine        = ($_.engine).ToString()
      }
    }
    $Items.FPolicy.FullData = $Items.Fpolicy.Formatted | Format-Table VServer, "Policy Name", Status, Engine | Out-String -Stream | Add-Indentation
  } Else {
    $Items.FPolicy.FullData = "`n$Spacer No Results Returned.`n"
  }
  $Items.FPolicy.Summary = New-Object -TypeName psobject -Property @{
    Item = "FPolicy Configured"
    Finding = ($Items.Fpolicy.Result.num_records -ne 0)
    Topic = $Categories[$Items.Fpolicy.Category]
  }

  # NAS Auditing
  $Items.NASAuditing.FullHeader = @"
$Separator
Recommendation: NAS auditing should be enabled
Reference: TR-4569 section "NAS file system auditing"
"@

  If ($Items.NASAuditing.Result.num_records -ne 0) {
    $Items.NASAuditing.Formatted = ForEach ($_ in $Items.NASAuditing.Result.records) {
      New-Object psobject -Property @{
        VServer = ($_.vserver).ToString()
        Enabled = ($_.state).ToString()
      }
    }
    $Items.NASAuditing.FullData = $Items.NASAuditing.Formatted | Format-Table VServer, Enabled | Out-String -Stream | Add-Indentation
  } Else {
    $Items.NASAuditing.FullData = "`n$Spacer No NAS Auditing Configuration Found.`n"
  }
  $Items.NASAuditing.Summary = New-Object -TypeName psobject -Property @{
    Item = "NAS Auditing Configured"
    Finding = ($Items.NASAuditing.Result.num_records -ne 0)
    Topic = $Categories[$Items.NASAuditing.Category]
  }

  # NIS
  $Items.NISServers.FullHeader = @"
$Separator
Recommendation: NIS should not be configured
Reference: TR-4569 section "Authentication methods"
"@

  If ($Items.NISServers.Result.num_records -ne 0) {
    $Items.NISServers.Formatted = ForEach ($_ in $Items.NISServers.Result.records) {
      New-Object psobject -Property @{
        VServer   = ($_.name).ToString()
        NISDomain = ($_.nis.domain).ToString()
      }
    }
    $Items.NISServers.FullData = $Items.NISServers.Formatted | Format-Table vserver, nisdomain | Out-String -Stream | Add-Indentation
  } Else {
    $Items.NISServers.FullData = "`n$Spacer No NIS Configuration Found.`n"
  }
  $Items.NISServers.Summary = New-Object -TypeName psobject -Property @{
    Item = "NIS not Configured"
    Finding = ($Items.NISServers.Result.num_records -eq 0)
    Topic = $Categories[$Items.NISServers.Category]
  }
  ### End - File Access - Category 5

  ### Begin - Data Protection - Category 6
  # Snapshot Autodeletion
  $Items.SnapShotAutoDelete.FullHeader = @"
$Separator
Recommendation: Snapshot auto-deletion should not be enabled for data volumes
Reference: System Manager insights
"@

  If ($Items.SnapShotAutoDelete.Result.num_records -ne 0) {
    $Items.SnapShotAutoDelete.Formatted = ForEach ($_ in $Items.SnapShotAutoDelete.Result.records) {
      New-Object psobject -Property @{
        VServer = ($_.vserver).ToString()
        Enabled = ($_.enabled).ToString()
        Volume  = ($_.volume).ToString()
      }
    }
    $Items.SnapShotAutoDelete.FullData = $Items.SnapShotAutoDelete.Formatted | Format-Table VServer, Volume, Enabled | Out-String -Stream | Add-Indentation
  } Else {
    $Items.SnapShotAutoDelete.FullData = "`n$Spacer No Results Returned.`n"
  }
  $Items.SnapShotAutoDelete.Summary = New-Object -TypeName psobject -Property @{
    Item = "No Volumes with Snapshot Autodeletion Enabled"
    Finding = ($Items.SnapShotAutoDelete.Result.num_records -eq 0)
    Topic = $Categories[$Items.SnapShotAutoDelete.Category]
  }

  # Snapshot Policy
  $Items.NullSnapShotPolicy.FullHeader = @"
$Separator
Recommendation: All volumes should have Snapshot policies
Reference: System Manager insights
"@

  If ($Items.NullSnapShotPolicy.Result.num_records -ne 0) {
    $Items.NullSnapShotPolicy.Formatted = ForEach ($_ in $Items.NullSnapShotPolicy.Result.records) {
      New-Object psobject -Property @{
        Volume            = ($_.volume).ToString()
        "Snapshot Policy" = "-"
        VServer           = ($_.vserver).ToString()
      }
    }
    $Items.NullSnapShotPolicy.FullData = $Items.NullSnapShotPolicy.Formatted | Format-Table volume, "Snapshot Policy", VServer | Out-String -Stream | Add-Indentation
  } Else {
    $Items.NullSnapShotPolicy.FullData = "`n$Spacer No Volumes with a Snapshot Policy of NULL."
  }
  If ($Items.NoneSnapShotPolicy.Result.num_records -ne 0) {
    $Items.NoneSS.Formatted = ForEach ($_ in $Items.NoneSnapShotPolicy.Result.records) {
      New-Object psobject -Property @{
        Volume            = ($_.volume).ToString()
        "Snapshot Policy" = ($_.snapshot_policy).ToString()
        VServer           = ($_.vserver).ToString()
      }
    }
    $Items.NoneSnapShotPolicy.FullData = $Items.NoneSS.Formatted | Format-Table volume, "Snapshot Policy", VServer | Out-String -Stream | Add-Indentation
  } Else {
    $Items.NoneSnapShotPolicy.FullData = "`n$Spacer No Volumes with a Snapshot Policy of None.`n"
  }
  $Items.NullSnapShotPolicy.Summary = New-Object -TypeName psobject -Property @{
    Item = "No Volumes with Snapshot Policy of NULL"
    Finding = ($Items.NullSnapShotPolicy.Result.num_records -eq 0)
    Topic = $Categories[$Items.NullSnapShotPolicy.Category]
  }
  $Items.NoneSnapShotPolicy.Summary = New-Object -TypeName psobject -Property @{
    Item = "No Volumes with Snapshot Policy of none"
    Finding = ($Items.NoneSnapShotPolicy.Result.num_records -eq 0)
    Topic = $Categories[$Items.NoneSnapShotPolicy.Category]
  }

  # Snapshot Locking
  $Items.SnapShotLocking.FullHeader = @"
$Separator
Recommendation: snapshot-locking-enabled should be true for all volumes with snapshots
Reference: TR-4569 section "Snapshot copy locking"
"@

  If ($Items.SnapShotLocking.Supported) {
    If ($Items.SnapShotLocking.Result.num_records -ne 0) {
      $Items.SnapShotLocking.Formatted = ForEach ($_ in $Items.SnapShotLocking.Result.records) {
        New-Object psobject -Property @{
          VServer                    = ($_.vserver).ToString()
          Volume                     = ($_.volume).ToString()
          "Snapshot Locking Enabled" = ($_.snapshot_locking_enabled).ToString()
        }
      }
      $Items.SnapShotLocking.FullData = $Items.SnapShotLocking.Formatted | Format-Table VServer, Volume, "Snapshot Locking Enabled" | Out-String -Stream | Add-Indentation
      $Items.SnapShotLocking.Summary = New-Object -TypeName psobject -Property @{
        Item = "Snapshot Locking Enabled for all Volumes"
        Finding = (!$Items.SnapShotLocking.Formatted."Snapshot Locking Enabled".contains("False"))
        Topic = $Categories[$Items.SnapShotLocking.Category]
      }
    } Else {
    }
  } Else {
    $Items.SnapShotLocking.FullData = "`n$Spacer Snapshot Copy Locking is not supported on this release. Consider upgrading to 9.12.1 or later.`n"
    $Items.SnapShotLocking.Summary = New-Object -TypeName psobject -Property @{
      Item = "Snapshot Locking Enabled for all Volumes"
      Finding = "Not available in this release"
      Topic = $Categories[$Items.SnapShotLocking.Category]
    }
  }
  ### End - Data Protection - Category 6

  ### Begin - Anti-Ransomware - Category 7
  # SVM Anti-Ransomware
  $Items.SVMAntiRansomware.FullHeader = @"
$Separator
Recommendation: SVMs should be configured for anti-ransomware
Reference: System Manager insights
"@

  If ($Items.SVMAntiRansomware.Supported) {
    If ($Items.SVMAntiRansomware.Result.num_records -ne 0) {
      $Items.SVMAntiRansomware.Formatted = ForEach ($_ in $Items.SVMAntiRansomware.Result.records) {
        New-Object psobject -Property @{
          VServer                = ($_.name).ToString()
          "Default Volume State" = ($_.anti_ransomware_default_volume_state).ToString()
        }
      }
      $Items.SVMAntiRansomware.FullData = $Items.SVMAntiRansomware.Formatted | Format-Table VServer, "Default Volume State" | Out-String -Stream | Add-Indentation
      $Items.SVMAntiRansomware.Summary = New-Object -TypeName psobject -Property @{
        Item = "Ransomware Protection Enabled for all SVMs"
        Finding = (!$Items.SVMAntiRansomware.Formatted."Default Volume State".contains("disabled"))
        Topic = $Categories[$Items.SVMAntiRansomware.Category]
      }
    }
  } Else {
    $Items.SVMAntiRansomware.FullData = "`n$Spacer Ransomware Protection is not supported on this release. Consider upgrading to 9.10 or later.`n"
    $Items.SVMAntiRansomware.Summary = New-Object -TypeName psobject -Property @{
      Item = "Ransomware Protection Enabled for all SVMs"
      Finding = "Not available in this release"
      Topic = $Categories[$Items.SVMAntiRansomware.Category]
    }
  }

  # Volume Anti-Ransomware
  $Items.VolumeAntiRansomware.FullHeader = @"
$Separator
Recommendation: Volume anti-ransomware-state should be enabled
Reference: System Manager insights
"@

  If ($Items.VolumeAntiRansomware.Supported) {
    If ($Items.VolumeAntiRansomware.Result.num_records -ne 0) {
      $Items.VolumeAntiRansomware.Formatted = ForEach ($_ in $Items.VolumeAntiRansomware.Result.records) {
        New-Object psobject -Property @{
          VServer                 = ($_.vserver).ToString()
          Volume                  = ($_.volume).ToString()
          "Anti Ransomware State" = ($_.anti_ransomware_state).ToString()
        }
      }
    }
    $Items.VolumeAntiRansomware.FullData = $Items.VolumeAntiRansomware.Formatted | Format-Table VServer, Volume, "Anti Ransomware State" | Out-String -Stream | Add-Indentation
    $Items.VolumeAntiRansomware.Summary = New-Object -TypeName psobject -Property @{
      Item = "Ransomware Protection Enabled for all Volumes"
      Finding = (!$Items.VolumeAntiRansomware.Formatted."Anti Ransomware State".contains("disabled"))
      Topic = $Categories[$Items.VolumeAntiRansomware.Category]
    }
  } Else {
    $Items.VolumeAntiRansomware.FullData = "`n$Spacer Ransomware Protection is not supported on this release. Consider upgrading to 9.10 or later.`n"
    $Items.VolumeAntiRansomware.Summary = New-Object -TypeName psobject -Property @{
      Item = "Ransomware Protection Enabled for all Volumes"
      Finding = "Not available in this release"
      Topic = $Categories[$Items.VolumeAntiRansomware.Category]
    }
  }
  ### End - Anti-Ransomware - Category 7

  ### - Begin - Encryption - Category 8
  # Key Manager
  $Items.KeyManager.FullHeader = @"
$Separator
Recommendation: A key-manager should be configured and encryption should be enabled at either the disk, aggregate,
                or volume layer
Reference: TR-4569 section "Storage encryption"
"@

  If ($Items.KeyManager.Result.num_records -ne 0) {
    $Items.KeyManager.FullData = "`n$Spacer Key-manager is configured."
  } Else {
    $Items.KeyManager.FullData = "`n$Spacer No Key-manager Found."
  }
  $Items.KeyManager.Summary = New-Object -TypeName psobject -Property @{Item = "Key-Manager Configured"
    Finding = ($Items.KeyManager.Result.num_records -ne 0)
    Topic = $Categories[$Items.KeyManager.Category]
  }

  # Drive Protection
  If ($Items.DriveProtection.Result.num_records -ne 0) {
    $Items.DriveProtection.Formatted = ForEach ($_ in $Items.DriveProtection.Result.records) {
      New-Object psobject -Property @{
        Aggregate                  = ($_.aggregate).ToString()
        Node                       = ($_.node).ToString()
        "Drive Protection Enabled" = ($_.drive_protection_enabled).ToString()
      }
    }
    $Items.DriveProtection.FullData = $Items.DriveProtection.Formatted | Format-Table Aggregate, Node, "Drive Protection Enabled" | Out-String -Stream | Add-Indentation
  } Else {
    $Items.DriveProtection.FullData = "`n$Spacer No Results Returned.`n"
  }
  $Items.DriveProtection.Summary = New-Object -TypeName psobject -Property @{
    Item = "Drive Encryption Enabled for all Aggregates"
    Finding = (!$Items.DriveProtection.Formatted."Drive Protection Enabled".contains("False"))
    Topic = $Categories[$Items.DriveProtection.Category]
  }

  # Volume Encryption
  If ($Items.VolumeEncryption.Result.num_records -ne 0) {
    $Items.VolumeEncryption.Formatted = ForEach ($_ in $Items.VolumeEncryption.Result.records) {
      New-Object psobject -Property @{
        VServer           = ($_.vserver).ToString()
        Volume            = ($_.volume).ToString()
        "Encryption Type" = ($_.encryption_type).ToString()
        "Is Encrypted"    = ($_.is_encrypted).ToString()
      }
    }
    $Items.VolumeEncryption.FullData = $Items.VolumeEncryption.Formatted | Format-Table VServer, Volume, "Encryption Type", "Is Encrypted" | Out-String -Stream | Add-Indentation
  } Else {
    $Items.VolumeEncryption.FullData = "`n$Spacer No Results Returned.`n"
  }
  $Items.VolumeEncryption.Summary = New-Object -TypeName psobject -Property @{
    Item = "Volume Encryption Enabled for all Volumes"
    Finding = (!$Items.VolumeEncryption.Formatted."Is Encrypted".contains("False"))
    Topic = $Categories[$Items.VolumeEncryption.Category]
  }
  ### - End - Encryption - Category 8
}

# Table Formatting
function Add-Indentation {
  process {
    $_ | ForEach-Object { ' ' * 8 + $_ }
  }
}

# Output to Text File
function Write-Data {
  process{
    $_ | Tee-Object ".\$Now.txt" -Append
  }
}

# Output Header
function Header {
  Write-Output $Header | Write-Data
}

# Output Summary
function SummaryOutput {
  $SummaryData = @()
  foreach ($key in $Items.Keys) {
    if ($($Items[$key].Summary)){
      $SummaryData += $Items[$key].Summary
    }
  }
  Write-Output "Summary`n$Separator" | Write-Data
  Write-Output $SummaryData | Format-Table Item, Finding, Topic -HideTableHeaders | Out-String -Stream | Add-Indentation | Write-Data
  Write-Output $Separator | Write-Data
}

# Output Full Data
function FullOutput{
  Write-Output "Full Details" | Write-Data
  foreach ($Index in $Categories.Keys){
    Write-Output "$Separator`n-- $($Categories.$Index) --" | Write-Data
    foreach ($key in $Items.Keys) {
      if ($($Items[$key].Category) -eq $Index){
        Write-Output $Items[$key].FullHeader | Write-Data
        Write-Output $Items[$key].FullData | Write-Data
      }
    }
  }
}

# Start Collection and Processing
CollectData
ProcessData

# Choices for output type
$Title = "Would you like full or summary output?"
$Choices = [System.Management.Automation.Host.ChoiceDescription[]] @("&Summary", "&Full", "&All")
$Default = 0
$Choice = $host.UI.PromptForChoice($Title, $Prompt, $Choices, $Default)
switch ($Choice) {
  0 {
    Header
    SummaryOutput
  }
  1 {
    Header
    FullOutput
  }
  2 {
    Header
    SummaryOutput
    FullOutput
  }
}