Monday, October 3, 2016

Track disk utilization across (Unix-like) systems, Specifically Tegile Intelliflash SAN.

I had a need to track the disk utilization over time on our SAN. The reports that come out of it are not sufficient for what I wanted to do.

Many years ago while I worked for Kaiser Permanente I had created a process that used some DOS batch scripts to pull down disk usage from a bunch of servers and converted it to Excel graphs to show monthly use.  I wanted a similar output for this data.

Turns out this will work for almost any Linux or Unix-like OS so feel free to use it for whatever.  Note that this script is tailored for Tegile Intelliflash/Zebi systems but can be easily modified.

Since I'm using Windows 10 now I've started to convert all my scripts to PowerShell v5, this one is no exception so be aware that there is a v5 requirement statement.  It will likely work with v4 but no guarantee.  Also the Posh-SSH module is REQUIRED.

There is an example config file at the bottom of the script.  The config file is required.  It's how I'm anonymising my scripts now.

Excel is required on the system the script executes from.  The systems in the target list in the config file are cycled through via SSH and the DF command is run. That output is collected and dropped into the spreadsheet.

Items to note:
  • If no spreadsheet exists it is created.
  • If it exists it will be appended to.
  • Target systems and target volumes (pools) need to be specified in the config file.
  • A spreadsheet is created for each month and named as such.
  • Each tab is a separate day of the month and labeled as such.
  • Best run as a scheduled task, but can be run standalone.
  • If run more than once a day that days data will be over written with the current data.
  • The file emails itself to the specified email accounts at the end of each month.
Updated versions are now located here: https://www.powershellgallery.com

<#==============================================================================
         File Name : Zebi-Utilization-Tracker.ps1
   Original Author : Kenneth C. Mazie (kcmjr AT kcmjr.com)
                   :
       Description : This script will query multiple Tegile Zebi Controllers,
                   : (or any Linux host) and run the "df -h" command capturing the
                   : results and parsing them into a spreadsheet.  Each tab is a
                   : different day.  Each tab is a list of all target controllers.
                   : A new spreadsheet is created each month.  Multiple runs in
                   : the same day over-write the existing data.
                   :
         Arguments : Named commandline parameters:  (all are optional)
                   :   "-console" - Displays console output during run.
                   :   "-debug" - Switches email recipient and some criteria.
                   :
             Notes : Settings are loaded from an XML file located in the script folder.
                   : See the end of the script for config file example.
                   :
      Requirements : REQUIRES Posh-SSH v1.7.6
                   : Requires Powershell v5
                   : 
          Warnings : None.  All operations are read only.
                   :
             Legal : Public Domain. Modify and redistribute freely. No rights reserved.
                   : SCRIPT PROVIDED "AS IS" WITHOUT WARRANTIES OR GUARANTEES OF
                   : ANY KIND. USE AT YOUR OWN RISK. NO TECHNICAL SUPPORT PROVIDED.
                   :
           Credits : Code snippets and/or ideas came from many sources around the web.
                   :
    Last Update by : Kenneth C. Mazie (email kcmjr AT kcmjr.com for comments or to report bugs)
   Version History : v1.0 - 09-12-16 - Original
    Change History : v2.0 - 09-27-16 - Major rewrite.  Reordered tabs.  Added free and used tabs
                   :     and moved them to the left.  Converted data from whatever to GB.
                   :
#===============================================================================#>
#requires -version 5.0

Param (
      [switch]$Debug = $false,
      [switch]$Console = $false
)

clear-host
If (!(Get-Module Posh-SSH)){Import-Module "Posh-SSH" -ErrorAction SilentlyContinue}
If ($Debug){$Script:Debug = $true}
If ($Console){$Script:Console = $true}
$ErrorActionPreference = "stop" #silentlycontinue"
$Script:DateToday = Get-Date -Format "MM-dd-yyyy"
$Script:ThisMonth = ((Get-Date -Format y) -replace (', ', '-')) -replace (' ','-')
$Script:ThisDay = (Get-Date -Format f).Split(",")[0]
$Script:Targets = @()
$Script:PoolList = @()

#--[ Excel Non-Interactive Fix ]------------------------------------------------
If (!(Test-path -Path "C:\Windows\System32\config\systemprofile\Desktop")){New-Item -Type Directory -Name "C:\Windows\System32\config\systemprofile\Desktop"}
If (!(Test-path -Path "C:\Windows\SysWOW64\config\systemprofile\Desktop")){New-Item -Type Directory -Name "C:\Windows\SysWOW64\config\systemprofile\Desktop"}
#--[ Excel will crash when run non-interactively via a scheduled task if these folders don't exist ]--

#--[ Functions ]----------------------------------------------------------------
Function SendEmail {
      If ($Script:Debug){$ErrorActionPreference = "stop"}
      If ($Script:EnableEmail){
            $email = New-Object System.Net.Mail.MailMessage
            $email.From = $Script:EmailFrom
            $email.IsBodyHtml = $Script:EmailHTML
            $email.To.Add($Script:EmailTo)
            $email.Subject = $Script:EmailSubject
            $email.Body = "Attached is the weekly Zebi utilization report."
            If ($Script:EmailAttach){
                  $email.Attachments.Add("$PSScriptRoot\$Script:ThisMonth.xlsx")
            }    
            $smtp = new-object Net.Mail.SmtpClient($Script:SmtpServer)
            $smtp.Send($email)
            If ($Script:Console){Write-Host "`nEmail sent...`n"}
      }    
}

Function Convert ($Incomming){    #--[ Convert TG and MB to GB ]--
      $Multiplier = $Incomming.substring($Incomming.length - 1, 1)
      $Number = $Incomming -replace ".$"
      If($Multiplier -eq "T"){$Script:Converted = [int]$Number*1024}
      If($Multiplier -eq "G"){$Script:Converted = [int]$Number*1024}
      If($Multiplier -eq "M"){$Script:Converted = [int]$Number/1024}
      Return $Outgoing
}

#--[ Read and load configuration file ]-----------------------------------------
If (!(Test-Path "$PSScriptRoot\Configuration.xml")){                            #--[ Error out if configuration file doesn't exist ]--
      Write-host "MISSING CONFIG FILE.  Script aborted." -ForegroundColor red
      break
}Else{
      [xml]$Script:Configuration = Get-Content "$PSScriptRoot\Configuration.xml"  #--[ Load configuration ]--
      $Script:EnableEmail = $Script:Configuration.Settings.Email.Enable
      $Script:DebugEmail = $Script:Configuration.Settings.Email.Debug
      $Script:EmailTo = $Script:Configuration.Settings.Email.To
      $Script:EmailHTML = $Script:Configuration.Settings.Email.HTML
      $Script:EmailSubject = $Script:Configuration.Settings.Email.Subject
      $Script:EmailFrom = $Script:Configuration.Settings.Email.From
      $Script:EmailAttach = $Script:Configuration.Settings.Email.Attach
      $Script:SmtpServer = $Script:Configuration.Settings.Email.SmtpServer
      $Script:UserName = $Script:Configuration.Settings.Credentials.Username
      $Script:Password = $Script:Configuration.Settings.Credentials.Password
      [array]$Script:Targets = $Script:Configuration.Settings.General.Targets
      $Script:WeeksBack = $Script:Configuration.Settings.General.Weeks
      [array]$Script:PoolList = $Script:Configuration.Settings.General.Pools
}    
#-------------------------------------------------------------------------------
#$Credential = New-Object PSCredential($UserName, (ConvertTo-SecureString $Password.SubString(64) -k ($Password.SubString(0,64) -split "(?<=\G[0-9a-f]{2})(?=.)" | % { [Convert]::ToByte($_,16) })))
#$Credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $UserName, ($Password | ConvertTo-SecureString)

$MissingType = [System.Type]::Missing
$Excel = New-Object -ComObject Excel.Application
If ($Script:Console -or $Script:Debug){
      $Excel.visible = $True
      $Excel.DisplayAlerts = $true
      $Excel.ScreenUpdating = $true
      $Excel.UserControl = $true
      $Excel.Interactive = $true
}Else{
      $Excel.visible = $False
      $Excel.DisplayAlerts = $false
      $Excel.ScreenUpdating = $false
      $Excel.UserControl = $false
      $Excel.Interactive = $false
}

If (Test-Path "$PSScriptRoot\$Script:ThisMonth.xlsx"){
      Rename-Item -Path "$PSScriptRoot\$Script:ThisMonth.xlsx" -NewName "$PSScriptRoot\$Script:ThisMonth-old.xlsx"
      $Workbook = $Excel.Workbooks.Open("$PSScriptRoot\$Script:ThisMonth-old.xlsx")
}Else{
      $Workbook = $Excel.Workbooks.Add()
      $Worksheet = $Workbook.Worksheets.Add()
      foreach ($Worksheet in $Workbook.Worksheets){
            If ($Worksheet.Name -eq "Sheet1"){
                  $Workbook.Worksheets.item("Sheet1").Activate()
                  $Worksheet.name = "Used"
            }
            If ($Worksheet.Name -eq "Sheet2"){
                  $Workbook.Worksheets.item("Sheet2").Activate()
                  $Worksheet.name = "Free"
            }
      }
}

$ReRun = $false
ForEach ($Worksheet in $Workbook.Worksheets){If ($Worksheet.Name -eq $DateToday){$ReRun = $true}}

If ($ReRun){   #--[ Already run today ]--
      if ($Console){Write-host "-- Re-Running Todays Data -- " -ForegroundColor Yellow }
      $Worksheet = $workbook.Sheets.Item($DateToday)
      $Worksheet.Activate()        
}Else{
      $Workbook.Worksheets.add([System.Reflection.Missing]::Value,$Workbook.worksheets.Item($Workbook.worksheets.count))
      ForEach ($Worksheet in $Workbook.Worksheets){
            If ($Worksheet.Name -like "*Sheet*"){$Worksheet.name = $DateToday}
      }    
}

$TargetCount = $Script:Targets.Target.count

$Row = 1
$Col = 1
$WorkSheet.Cells.Item($Row,$Col) = "Controller:"
$WorkSheet.Cells.Item($Row,$Col).font.bold = $true
$WorkSheet.Cells.Item($Row,$Col).HorizontalAlignment = 1
$Col++
$WorkSheet.Cells.Item($Row,$Col) = "Filesystem:"
$WorkSheet.Cells.Item($Row,$Col).font.bold = $true
$WorkSheet.Cells.Item($Row,$Col).HorizontalAlignment = 1
$Col++
$WorkSheet.Cells.Item($Row,$Col) = "Size GB:"
$WorkSheet.Cells.Item($Row,$Col).font.bold = $true
$WorkSheet.Cells.Item($Row,$Col).HorizontalAlignment = 1
$Col++     
$WorkSheet.Cells.Item($Row,$Col) = "Used GB:"
$WorkSheet.Cells.Item($Row,$Col).font.bold = $true
$WorkSheet.Cells.Item($Row,$Col).HorizontalAlignment = 1
$Col++
$WorkSheet.Cells.Item($Row,$Col) = "Available GB:"
$WorkSheet.Cells.Item($Row,$Col).font.bold = $true
$WorkSheet.Cells.Item($Row,$Col).HorizontalAlignment = 1
$Col++
$WorkSheet.Cells.Item($Row,$Col) = "Capacity:"
$WorkSheet.Cells.Item($Row,$Col).font.bold = $true
$WorkSheet.Cells.Item($Row,$Col).HorizontalAlignment = 1
$Col++
$WorkSheet.Cells.Item($Row,$Col) = "Mount Point:"
$WorkSheet.Cells.Item($Row,$Col).font.bold = $true
$WorkSheet.Cells.Item($Row,$Col).HorizontalAlignment = 1
$Col++     
$WorkSheet.application.activewindow.splitcolumn = 0
$WorkSheet.application.activewindow.splitrow = 1
$WorkSheet.application.activewindow.freezepanes = $true
$Resize = $WorkSheet.UsedRange
[void]$Resize.EntireColumn.AutoFit()

$Target = @()
$Row = 2
Foreach ($Target in $Script:Targets.Target){
      if ($Console){Write-Host "`n--[ Processing Target: $Target ]-----------------------------------" -ForegroundColor Yellow }
      $Count = 1
     
      $Cmd = 'df -h'             #--[ The command to run over SSH ]--
      #--[ Some optional other commands that could be used ]--
      #   '/usr/sbin/zpool list'
      #     'cat /var/log/tegile/zebi/zebirep.log | egrep "has completed|has failed"'    #--[ to purge replication logs ]--
      #     'uname -a '
      #     '/usr/sbin/zfs list -o name'
      #     'pwd'
      #     'df -h'
      #     '/usr/sbin/dladm show-link –S'
      #     '/usr/sbin/fmadm faulty'
      #     '/usr/sbin/zpool list'
      #     '/usr/sbin/zfs list -o name'
      #     'zfs list -t snapshot'
      #     'zfs list -o space -r hostname$/Local'
      #     'zpool iostat 2'                        #--[ Display ZFS I/O statistics every 2 seconds ]--
      #     'zpool iostat -v 2'               #--[ Display detailed ZFS I/O statistics every 2 seconds ]--

      Remove-SSHSession -SessionId 0 -ErrorAction SilentlyContinue | Out-Null     #--[ Clear out previous session if it still exists ]--

      $secpasswd = ConvertTo-SecureString $Script:Password -AsPlainText -Force
      $mycreds = New-Object System.Management.Automation.PSCredential ($Script:UserName, $secpasswd)
      $SSH = New-SshSession -ComputerName $Target -Credential $mycreds -AcceptKey:$true # | Out-Null  #--[ Open new SSH session ]--
     
      $Script:Converted = $(Invoke-SSHCommand -SSHSession $SSH -Command $Cmd).Output        #--[ Invoke SSH command and capture the output as a string ]--

      $Col = 1
      $Counter = 1
      ForEach ($Line in $Script:Converted ){
      $Line = $Line -Replace ('\s+', ' ')
      $Line = $Line -Split " "
      if ($Console){Write-Host "   Current input line :"$Line -ForegroundColor cyan }
      if ($Console){write-host "   Input pool list    :"$Script:PoolList.Pool -ForegroundColor Red }
      if ($Console){write-host "   Match criteria     :"$Line.split("/")[0] -ForegroundColor Magenta}
     
      if ($Script:PoolList.Pool -contains($Line.split("/")[0]) -and ($Line.split(" ")[5].split("/")[1] -Like "export" )){
            if ($Console){Write-Host "   -- Match found, using line. --" -ForegroundColor yellow}
            if ($Counter -gt 1){                      #--[ Dump collected data into spreadsheet ]--
                  #--[ Controller ]-------------------------------------------------------               
                  $WorkSheet.Cells.Item($Row,$Col) = "$Script:Target"
                  $Col++
                  #--[ FileSystem ]-------------------------------------------------------         
                  $WorkSheet.Cells.Item($Row,$Col) = $Line[0]
                  $Col++           
                  #--[ Size ]-------------------------------------------------------------
                  $Return = Convert $Line[1]
                  $WorkSheet.Cells.Item($Row,$Col) = $Script:Converted
                  #$WorkSheet.Cells.Item($Row,$Col) = $Line[1]
                  $Col++
                  #--[ Used ]-------------------------------------------------------------
                  $Return = Convert $Line[2]
                  $WorkSheet.Cells.Item($Row,$Col) = $Script:Converted
                  #$WorkSheet.Cells.Item($Row,$Col) = $Line[2]
                  $Col++
                  #--[ Available ]--------------------------------------------------------
                  $Return = Convert $Line[3]
                  $WorkSheet.Cells.Item($Row,$Col) = $Script:Converted
                  #$WorkSheet.Cells.Item($Row,$Col) = $Line[3]
                  $Col++
                  #--[ Capacity ]---------------------------------------------------------
                  $WorkSheet.Cells.Item($Row,$Col) = $Line[4]
                  $Col++
                  #--[ MountPoint ]-------------------------------------------------------         
                  $WorkSheet.Cells.Item($Row,$Col) = $Line[5]
                  $Col++

                  sleep -milliseconds 100
                  $Line = ""
                  $RepJob = ""
                  $Status = ""
                  $Col = 1
                  $Resize = $WorkSheet.UsedRange
                  [void]$Resize.EntireColumn.AutoFit()
            }
            $Row++
      }Else{
            if ($Console){Write-Host "   -- No Match found. --" -ForegroundColor DarkGray }
      }
      $Counter ++
      }
Remove-SSHSession -SessionId 0 -ErrorAction SilentlyContinue | Out-Null
}


#--[ Populate FREE and USED tabs ]-------------------------------------
$Col = ((Get-Date).Day)+1  
$Row = 1
#--[ Copy Filesystem Names  ]---
$Worksheet0 = $workbook.WorkSheets.Item($DateToday)
$Range0A = $WorkSheet0.Range("B1").EntireColumn()
#--[ Paste Filesystem Names to USED ]---
$Range0A.Copy()
$Worksheet1 = $Workbook.Worksheets.Item("Used")
$Range1A = $WorkSheet1.cells.Item(1,1)
$Worksheet1.Paste($Range1A)
#$WorkSheet1.cells.Item($Row,$Col) = $Col
#--[ Copy Todays Data, Paste to USED ]--
$Range1B = $WorkSheet0.Range("D2").EntireColumn()
$Range1B.Copy()
$Range1B = $WorkSheet1.cells.Item($Row,$Col)
$Worksheet1.Paste($Range1B)
$WorkSheet1.cells.Item($Row,$Col) = ($Col-1)
#--[ Paste Filesystem Names to FREE ]---
$Range0A.Copy()
$Worksheet2 = $Workbook.Worksheets.Item("Free")
$Range2A = $WorkSheet2.cells.Item(1,1)
$Worksheet2.Paste($Range2A)
#$WorkSheet2.cells.Item($Row,$Col) = $Col
#--[ Copy Todays Data, Paste to FREE ]--
$Range2B = $WorkSheet0.Range("E2").EntireColumn()
$Range2B.Copy()
$Range2B = $WorkSheet2.cells.Item($Row,$Col)
$Worksheet2.Paste($Range2B)
$WorkSheet2.cells.Item($Row,$Col) = ($Col-1)


#--[ Save and Quit ]--
$Workbook.SaveAs("$PSScriptRoot\$Script:ThisMonth.xlsx")
$Excel.Quit()
$Excel = $Null

If (Test-Path "$PSScriptRoot\$Script:ThisMonth-old.xlsx"){Remove-Item -Path "$PSScriptRoot\$Script:ThisMonth-old.xlsx" -Confirm:$false -Force:$true}

Sleep -Seconds 2
If ($Script:ThisDay -eq "Sunday"){SendEmail}    #--[ Email report on Sundays ]--

[gc]::Collect() | Out-Null
[gc]::WaitForPendingFinalizers() | Out-Null

if ($Console){Write-Host "--- Completed ---" -ForegroundColor Red }

<#-----------------------------[ Config File ]---------------------------------

The configuration file must be named "Configuration.xml" and must reside in
the same folder as the script.  Below is the format and element list:

<!-- Settings & Configuration File -->
<Settings>
      <General>
            <ScriptName>SAN-Utilization-Tracker</ScriptName>
            <DebugTarget>test-server</DebugTarget>
            <Targets>
                  <Target>10.100.1.1</Target>
                  <Target>10.100.1.2</Target>
                  <Target>10.100.1.3</Target>
                  <Target>10.100.1.4</Target>
                  <Target>10.100.1.5</Target>
                  <Target>10.100.1.6</Target>
                  <Target>10.100.1.7</Target>
                  <Target>10.100.1.8</Target>
            </Targets>
            <Pools>
                  <Pool>Pool1</Pool>
                  <Pool>Pool2</Pool>
            <Pools>
            <Weeks>2</Weeks>
      </General>
      <Email>
            <Enable>$true</Enable>
            <From>SANReports@mydomain.com</From>
            <To>me@mydomain.com</To>
            <Subject>Zebi Utilization Status Report</Subject>
            <HTML>$true</HTML>
            <Attach>$true</Attach>
            <SmtpServer>10.10.1.10</SmtpServer>
      </Email>
      <Credentials>
            <UserName>zebiadminuser</UserName>
            <Password>zebiadminpwd</Password>
      </Credentials>
</Settings>


#>







Tuesday, September 27, 2016

Cloud Storage (Warning...Gratuitous ad)

Forgive me for the gratuitous advertisement but I am a fan of cloud storage.  It's still in it's infancy but the way technology changes these days the sky's the limit, and we haven't seen anything yet.

Anyway, I'm extremely paranoid by nature so I won't store anything in the cloud unless it's pre-encrypted.  As much as I love the concept I still just don't feel comfortable storing stuff on these sites.  As a result I use SkyCrypt and BoxCryptor to pre-encrypt everything that goes to the cloud.

One thing I found that's pretty slick is a service that can link cloud services called "MultCloud".  It allows you to move files between cloud services.  They have a deal going right now where if I post a blurb about their service I can get my account bumped up to 10 Terabytes of cross-traffic.  What the heck, I can use the bandwidth so here goes.

MultCloud(https://www.multcloud.com/), a FREE and easy-to-use web app, supports for Managing Files and Transferring Files across Cloud Drives. Free provide 10TB traffic for data transmission.

Again, sorry for the gratuitous ad but a 10 Terabyte upgrade was too much to pass up...  :-)


Tuesday, September 20, 2016

How to grow the disk on the Cisco FirePower VM Appliance

I had to expand the disk on our Cisco FirePower appliance today to accommodate additional logging.  Initially I had a lot of trouble getting things to cooperate.  It took some time and searching Google to identify why I was having issues.  The answers were contained within about 4 different posts I located.

This appliance starts life as an OVF file that you download and deploy.  It's based on a heavily modified version of Redhat Linux. As a result these steps would apply to any Linux extended partition.

The logs are stored under the /var folder which is mounted from SDA7.  SDA7 is the third partition contained withing an extended partition.

The solution is fairly simple using a GParted live CD image so I figured I'd post it.

The process is as follows:

  1. Gracefully shut down the appliance.
  2. Snapshot it "just in case".
  3. Edit the VM settings to add a CD drive and force BIOS at next boot.  Extend the disk to whatever size is required at this time.
  4. Mount the GParted ISO image or CD depending on what you have.
  5. Boot the VM and edit the BIOS boot options to boot from CD first.  
  6. Save and reboot.
  7. The VM should now boot into the Live GParted CD.
  8. Load the partition editor.  You should see the single large disk and three partitions, twp primary and one extended which contains three partitions.
  9. Select the large partition within the extended partition.  This "should" be SDA7 but this may vary.
  10. SHRINK this partition by around one gigabyte.  More or less is fine.  (Note that this can take a LONG time.  On our fast systems it tool 2 hours.)
  11. Once completed MOVE the now smaller partition to the far right edge of the extended partition.
  12. Once complete MOVE both remaining partitions to the right.  You may need to reboot before these will let you move them.
  13. When all partitions have been move to the right you should be able to extend the container partition all the way out to the right.  Seems there must be a small amount of space to the far left before you can extend it.
  14. Now that the extended partition is fully expanded you can expand SDA7 to fill the remaining space.
  15. Reboot back into the OS and verify that things still work.
I followed these steps and everything went very smoothly.  

A few things to note.  You will get errors during the moves about kernel notifications.  Just ignore these.  Also if things start failing you may need to reboot between steps.  I also saw posts discussing the fact that should the Linux swap partition get mounted you won't be able to edit anything.  The live CD doesn't use the swap on the disks so it "should" just work.  If for some reason the swap partition does get mounted you can dismount it from within GParted.

Thats it.  Hope this helps someone else save some time.