Sunday, December 7, 2014

PowerShell domain disk statistics report

Ever needed an automated disk statistics report for your Windows domain?  I did.  I was able to gather bits and pieces from around the Internet and create the script below.

It's designed for my needs but can be easily edited to suit yours.  The basic functionality is to use WMI via PowerShell to poll servers, PCs, or both and grab disk usage for any system within the targeting parameters and generate an easy to read HTML report that is emailed to anyone needed.

Our environment is comprised of both physical and virtual systems.  I need to be able to spot systems running low on disk space with expandable disks (virtual) that can be quickly grown if needed.  The output color codes C: drives below 60 GB in red, that's my low threshold.  It also color codes physical verses virtual.  You can select between a full report of every system, or a brief option for only systems below threshold.  You can also filter by server or PC and turn console output on and off for debugging.

The script is internally documented.  It emails an HTML formatted report that shows each disk, its size, how much is used, and how much is left.

I run it as a daily scheduled job from a service account with domain admin rights.

I also have a variation of this report that details the disk block size.  Very handy when tuning your virtual disks for use on a SAN.  I'll publish that one later.

UPDATE: I altered the commandline arguments to be named.

Below is an example of the output as seen in email:





<#====================================================================
         File Name : DomainDiskReport.ps1
   Original Author : Kenneth C. Mazie
                   :
       Description : This script uses WMI to poll the current domain and extract
                   : disk statistics. Output is gathered in HTML format and emailed
                   : to a list of recipients.  Output can be all systems (full)
                   : or only systems that are below preset threshold (brief).
                   :
         Arguments : Named commandline parameters:  (all are optional)
                   :   "-debug" - Displays console output during run.  Switches email recipient.
                   :   "-mode" - Can be set to "full" or "brief". Default to "brief".
                   :   "-detail" - Can be "all", "pc", or "server".  Defaults to "all" which polls both.
                   :
     Notes : Allows setting a threshold of the lowest remaining space.  Colors all
                   :   others in red.
                   : Numerous parameters are adjustable from within the main function.
                   : !!! -- Best run from a scheduled job as a domain admin user -- !!!
                   :
          Warnings : None
                   : 
             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 - 05-03-14 - Original
    Change History : v2.0 - 11-01-14 - Changed HTML formatting. Added commandline options.
                   : v3.0 - 12-10-14 - Changed input arguments to be named
                   :
====================================================================#>

#--[ Set input variables ]--
Param (
   [switch]$Debug = $False,            #--[ Debug mode off unless specified ]--
   [string]$Detail = "brief",          #--[ Defaults to brief output ]--
   [string]$Mode = "all"               #--[ Defaults to reporting both server and PC ]--
 )

Clear-Host


new-variable -force -name startupVariables -value ( Get-Variable | % { $_.Name } )  

$startupVariables = ""

$ErrorActionPreference = "silentlycontinue"      
$Datetime = Get-Date -Format "MM-dd-yyyy_HH:mm"
$Global:eMailRecipient = "your full email here" 
$DebugEmailTarget = "your full email for debugging"
$Global:SMTPServer = "your-email-server"                     #--[ Email server address ]-

if (!(Get-Module -Name ActiveDirectory)){Import-Module ActiveDirectory}     #--[ Load the AD module if needed ]--


If ($Debug){$Global:eMailRecipient = $DebugEmailTarget}          #--[ Destination email address during debug. ]--

Function Cleanup-Variables {
  Get-Variable | Where-Object { $startupVariables -notcontains $_.Name } | % { Remove-Variable -Name "$($_.Name)" -Force -Scope "global" }
}

function Hide-Console {                               #--[ Stops console from displaying ]--
$consolePtr = [Console.Window]::GetConsoleWindow()
[void][Console.Window]::ShowWindow($consolePtr, 0)
}
hide-console

Function SendEmail {
   $email = New-Object System.Net.Mail.MailMessage
   $email.From = "DiskReport@$eMailDomain"
   $email.IsBodyHtml = $True
   $email.To.Add($Global:eMailRecipient)
   $email.Subject = "PowerShell Daily Disk Status Report"
   $email.Body = $Global:ReportBody
   $smtp = new-object Net.Mail.SmtpClient($Global:SMTPServer)
   $smtp.Send($email)
   If ($Debug){Write-Host "`n`rEmail sent..."}
}

Function Process ($Global:Target){
   $Flag = $False
   $Size = "";$Freespace="";$PercentFree="";$SizeGB="";$FreeSpaceGB="";$Win32_Hardware = "";$Win32_OS = ""
   $Global:Target=$Global:Target.Name
   $PercentWarning = "10"                    #--[ Issue warning if free disk space is below this % ]--
   $TargetHash = @{"Target" = $Global:Target}
 
   If(Test-Connection -ComputerName $Global:Target -count 1 -BufferSize 16 -ErrorAction SilentlyContinue ) {
      #--[ Gather system data ]----------------------------------------------------------------------
      $WMIJob = Get-WMIObject win32_logicaldisk -Filter "DriveType = 3" -ComputerName $Global:Target -AsJob
      Wait-Job -ID $WMIJob.ID -Timeout 5 | Out-Null
      $Disks = Receive-Job $WMIJob.ID -ErrorAction SilentlyContinue
      #--------------------------------------------------------------------------------------------
      $WMIJob = Get-WMIObject Win32_OperatingSystem -computer $Global:Target -AsJob
      Wait-Job -ID $WMIJob.ID -Timeout 5 | Out-Null
      $Win32_OS = Receive-Job $WMIJob.ID -ErrorAction SilentlyContinue | select Caption
      $Win32_OS = ($Win32_OS.caption -replace("\(R\)","")).Replace("Microsoft ","")
      $TargetHash.Add("OS",$Win32_OS)
      #--------------------------------------------------------------------------------------------
      $WMIJob = Get-WMIObject -ComputerName $Global:Target -class Win32_ComputerSystemProduct -AsJob
      Wait-Job -ID $WMIJob.ID -Timeout 5 | Out-Null
      $Win32_Hardware = Receive-Job $WMIJob.ID -ErrorAction SilentlyContinue       
      if(([string]::IsNullOrEmpty($Win32_Hardware.Name)) -or ($Win32_Hardware.Name -eq " ")){
         $MFG = "Unknown"
      }ElseIf ($Win32_Hardware.Name -eq "VMware Virtual Platform"){
         $MFG = "VMware Virtual"
      }Else{
         $MFG = $Win32_Hardware.Name.trimend()
      }
      $TargetHash.Add("MFG",$MFG)
      #--------------------------------------------------------------------------------------------
      $Count = 0
      foreach($Disk in $Disks){
         $DeviceID = $Disk.DeviceID;
         [float]$Size = $Disk.Size;
         [float]$Freespace = $Disk.FreeSpace;
         [string]$PercentFree = [Math]::Round(($Freespace / $Size) * 100, 0);
         [string]$SizeGB = [Math]::Round($Size / 1073741824, 0);
         [string]$FreeSpaceGB = [Math]::Round($Freespace / 1073741824, 2);                
         $DriveHash =  @{"Drive" = $DeviceID;"Size" = $SizeGB;"PercFree" = $PercentFree;"FreeSpace" = $FreeSpaceGB}
         $TargetHash.Add($Count,$DriveHash)  
         $Count++
      }  #--[End - ForEach disk ]--      

      #----------------------------------------- START OF OUTPUT -----------------------------------------------------
      If(($Global:Target -like "*whatever*") -and ($TargetHash.Item(0).Item("PercFree") -gt 10 )){   #--[ Bypass a name pattern ]--
      If ($Debug){
         Write-Host "`r`n--[ " -ForegroundColor White -NoNewline
         write-host $Global:Target.Toupper() -ForegroundColor Yellow -NoNewline
         Write-Host " ]-------------------------------------------------------------------".PadRight((110-$Global:Target.length),"-") -ForegroundColor White
         Write-Host $Global:Target " BYPASS MATCH.  c: %free " ($TargetHash.Item(0).Item("PercFree"))-ForegroundColor Magenta}
      }else{
         If ($Debug){
            Write-Host "`r`n--[ " -ForegroundColor White -NoNewline
            write-host $Global:Target.Toupper() -ForegroundColor Yellow -NoNewline
            Write-Host " ]-------------------------------------------------------------------".PadRight((110-$Global:Target.length),"-") -ForegroundColor White
         }         
         $HtmlData += '<td><font color="blue">' + $Global:Target + '</td>'
         If([string]::IsNullOrEmpty($Win32_OS)){$Win32_OS = "Unknown"}
         If (($Win32_OS -eq "Unknown") -and ($MFG -eq "Unknown")){
            If ($Debug){Write-Host "Failed to extract any information from target system..." -ForegroundColor cyan}
            $HtmlData += '<td colspan=2><center><font color="darkcyan">Failed to extract any information from target system.</center></td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td></tr>'
         }Else{
            If (($Win32_OS -like "*R2*") -or ($Win32_OS -like "*2012*") -or ($Win32_OS -like "*Windows 7*") -or ($Win32_OS -like "*Embedded*")){
            If ($Debug){Write-Host "OS = $Win32_OS".PadRight(50," ") -ForegroundColor Green -NoNewline }
            $HtmlData += '<td><font color="green">' + $Win32_OS + "</font></td>"
         }Else{
            #If([string]::IsNullOrEmpty($Win32_OS)){$Win32_OS = "Unknown"}
            If ($Debug){Write-Host "OS = $Win32_OS".PadRight(50," ") -ForegroundColor Red -NoNewline }
            $HtmlData += '<td><font color="red">' + $Win32_OS + "</font></td>"
         }         
                     
         If($MFG -eq "VMware Virtual")                  #--[ Unique coloring for VM ]--
            If ($Debug){Write-Host "Hardware = $MFG" -ForegroundColor cyan}
            $HtmlData += '<td><font color="green">VMware Virtual</font></td>'
         }Else{
            If ($Debug){Write-Host "Hardware = $MFG" -ForegroundColor Magenta}
            $HtmlData += '<td><font color="red">' + $MFG + '  </font></td>'
         }
          
         $Count = 0
         While ($Count -lt $TargetHash.count-3 ){
            #$TargetHash.Item($Count).Item("Drive")     #--[ Extra items if needed ]--
            #$TargetHash.Item($Count).Item("Size")
            #$TargetHash.Item($Count).Item("FreeSpace")
            #$TargetHash.Item($Count).Item("PercFree")
            If ($Debug){Write-Host "Drive =" $TargetHash.Item($Count).Item("Drive").PadRight(15," ") -NoNewline }
                                                          
            If ($Count -gt 0){
               $HtmlData += "<td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>" + $TargetHash.Item($Count).Item("Drive") +"</td>"
            }Else{
               $HtmlData += "<td>" + $TargetHash.Item($Count).Item("Drive") + "</td>"
            }
                                             
            #--[ report if server C: drive is less than 60 gb - #$TargetHash.Item($Count).Item("Size") ]--
            If (($DeviceID -eq "C:") -and ([int]$TargetHash.Item($Count).Item("Size") -lt 59) -and ($Win32_OS -like "*Server*")){
               If ($Debug){Write-Host "Size = "($TargetHash.Item($Count).Item("Size").PadRight(20," ")) -NoNewline -ForegroundColor red}
               $HtmlData += '<td><font color="red">' + $TargetHash.Item($Count).Item("Size") + '</font></td>'
            }Else{
               If ($Debug){Write-Host "Size = "($TargetHash.Item($Count).Item("Size").PadRight(20," ")) -NoNewline -ForegroundColor green}
               $HtmlData += '<td><font color="green">' + $TargetHash.Item($Count).Item("Size") + '</font></td>'
            }
         
            #--[ $TargetHash.Item($Count).Item("SpaceFree") ]--
            If ($Debug){Write-Host "FreeSpace =" $TargetHash.Item($Count).Item("FreeSpace").PadRight(20," ") -NoNewline }
            $HtmlData += "<td>" + $TargetHash.Item($Count).Item("FreeSpace") + "</td>"
                     
            #--[  $TargetHash.Item($Count).Item("PercFree") ]--
            If(([int]$TargetHash.Item($Count).Item("PercFree")) -lt ([int]$PercentWarning)){
               $Flag = $True
               If ($Debug){Write-Host "Percent Free = "(($TargetHash.Item($Count).Item("PercFree").PadLeft(2,"0")) + " %").PadRight(10," ") -ForegroundColor Red} # -NoNewline }
               $HtmlData += '<td><font color="red">' + $TargetHash.Item($Count).Item("PercFree") + ' %</font></td></tr>'
            }Else{
               If ($Debug){Write-Host "Percent Free = "(($TargetHash.Item($Count).Item("PercFree").PadLeft(2,"0")) + " %").PadRight(10," ") -ForegroundColor Green} # -NoNewline }
               $HtmlData += '<td><font color="green">' + $TargetHash.Item($Count).Item("PercFree") + ' %</font></td></tr>'
            }
            $Count++
            }
         }
      }#------------------ END OF OUTPUT ----------------------
      If (($Flag -and ($Detail -eq "brief")) -or ($Detail -ne "brief")){$Global:ReportBody += $HtmlData}
   }Else{
      If ($Debug){
         Write-Host "`r`n--[ " -ForegroundColor White -NoNewline
         write-host $Global:Target.Toupper() -ForegroundColor Yellow -NoNewline
         Write-Host " ]-------------------------------------------------------------------".PadRight((110-$Global:Target.length),"-") -ForegroundColor White
         Write-Host "Failed to sucessfully ping target system..." -ForegroundColor Cyan
      }         
      If($Detail -ne "brief"){   
         $Global:ReportBody += '<tr><td><font color="blue">' + $Global:Target + '</td><td colspan=2><center><font color="darkcyan">Failed to successfully ping target system.</center></td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td></tr>'
      }
      $Global:ReportBody += $HtmlData
   }  #--[ End - test-connection ]--
Return
}
#--[ End of Functions ]---------------------------------------------------------

$ExclusionList = @( #--[ Add systems here that come up as false positives or need to be excluded ]--
"server1",
"server2",
"pc1"
)

#--[ Add header to html log file ]--
$Global:ReportBody = @() | Select Target,Drive,SizeGB,FreeSpaceGB,PercentFree,Manufacturer
$Global:ReportBody += '<style type="text/css">
table.myTable { border:5px solid black;border-collapse:collapse; }
table.myTable td { border:2px solid black;padding:5px;background: #E6E6E6 }, table.myTable th { border:2px solid black;padding:5px;background: #A4A4A4 }
#table.bottomBorder { border-collapse:collapse; }
#table.bottomBorder td, table.bottomBorder th { border-bottom:1px dotted black;padding:5px; }
</style>
The following report displays disks configured on every server and PC in the domain.
<ul>
   <li>If the "brief" option was used only systems with disks in need of attention are listed.</li>
   <li>Operating systems that can be dynamically grown are shown in green, all others in red.</li>
   <li>Under the Hardware heading physical systems are noted in red since the disks cannot be grown.</li>
   <li>Any C: drives smaller than 60 GB are flagged in red.</li>
   <li>All other disks are red only if the remaining free disk space is below 10%.</li>
   <li>Systems that are not reachable for whatever reason are noted as such.</li>'

If($Detail -eq "brief"){
   $Global:ReportBody += '<li><strong>Note: Script was executed with the "brief" option enabled.</strong></li><br><br></ul>'
}Else{
   $Global:ReportBody += '</ul>'
}

$Global:ReportBody += 'Script executed on '+$Datetime+'<br><br>'

If (($Mode -eq "server") -or ($Mode -eq "all")){
   $Computers = Get-ADComputer -Filter {operatingsystem -like "*server*" -and name -notlike "*esx*"} | sort name
   $Global:ReportBody += '<table class="myTable"><tr><th>Target</th><th>Operating System</th><th>Hardware</th><th>Drive</th><th>SizeGB</th><th>FreeSpaceGB</th><th>PercentFree</th></tr>'
   ForEach($Global:Target in $Computers ){
      If ($ExclusionList -notcontains $Global:Target.name){
         Process $Global:Target
      }
   }
$Global:ReportBody += '</table><br><br>'
}

If (($Mode -eq "pc") -or ($Mode -eq "all")){
   $Computers = Get-ADComputer -Filter {operatingsystem -notlike "*server*" -and name -notlike "*esx*"} | sort name
   $Global:ReportBody += '<table class="myTable"><tr><th>Target</th><th>Operating System</th><th>Hardware</th><th>Drive</th><th>SizeGB</th><th>FreeSpaceGB</th><th>PercentFree</th></tr>'
   ForEach($Global:Target in $Computers ){
      If ($ExclusionList -notcontains $Global:Target.name){
         Process $Global:Target
      }
   }
   $Global:ReportBody += "</table>"
}

If ($Debug){$Global:ReportBody | Out-File c:\Scripts\temp.html}
SendEmail

#endregion
If ($Debug){Write-Host "--- COMPLETED ---" -ForegroundColor Red}

#Cleanup-Variables      #--[ Uncomment to reset pre-run variables ]--






Wednesday, October 29, 2014

Redhat / Centos ShellShock remediation

I realize it's old info now but I'm posting this for my own reference.  This info was gleaned from other sites.  I adapted the tests to display results in color.  Hey I'm old, it helps to see things  :-)

The tests are listed as Redhat or Centos but the same script should work on any version of Linux and probably Unix as well.

BASH ShellShock Remediation

Redhat/Centos version:
cat /etc/redhat-release

BASH version:
bash -version 
   Or
rpm -qa | grep bash

Test: 
clear;env x="() { :;}; echo -e '\033[1;31mVULNERABLE\033[0m'" "BASH_FUNC_x()=() { :;}; echo VULNERABLE" bash -c " echo -ShellShock-Test-"
   Or
clear;env X="() { :;} ; echo -e '\033[1;31mVULNERABLE\033[0m'" /bin/sh -c "echo -ShellShock-Test-"

Result before patching:
VULNERABLE
bash: BASH_FUNC_x(): line 0: syntax error near unexpected token `)'
bash: BASH_FUNC_x(): line 0: `BASH_FUNC_x() () { :;}; echo VULNERABLE'
bash: error importing function definition for `BASH_FUNC_x'
-ShellShock-Test-
    Or
VULNERABLE
-ShellShock-Test-

Remediation:
yum update bash

Result after patching:
bash: warning: X: ignoring function definition attempt
bash: error importing function definition for `X'
-ShellShock-Test-
   Or
-ShellShock-Test-

Friday, August 29, 2014

PowerShell won't run Excel from a scheduled task

Just ran head-on into what appears to be a bug in the .NET Excel object in PowerShell.



It appears to be a known bug but there is not a lot of info about it.  The bug does not allow Excel to be run from a task in windows task scheduler unless the task author has checked the “user has to be logged on to run” option.

The issue is that Excel tries to do a “CreateFile” operation in “C:\Windows\SysWOW64\config\systemprofile\AppData\Local\Microsoft\Windows\Temporary Internet Files”.  The problem is that the Temporary Internet Files folder doesn’t exist in that location.

Until Microsoft fixes this the solution work-around is to create two folders:

  • "C:\Windows\System32\config\systemprofile\Desktop" (Create on both 32 bit and 64 bit systems)
  • "C:\Windows\SysWOW64\config\systemprofile\Desktop" (Create only on 64 bit systems)

Once these are created and the scheduled task is re-run, everything should work fine regardless if you are running the task with "Run whether user is logged on or not" is checked.