PureStoragePowerShellToolkit.psm1
<#
=========================================================================== Created by: barkz@purestorage.com Organization: Pure Storage, Inc. Filename: PowerShell-Toolkit.psm1 Copyright: (c) 2016 Pure Storage, Inc. Github: https://github.com/purestorage/PowerShell-Toolkit ------------------------------------------------------------------------- Module Name: PowerShell-Toolkit Development Tool: https://github.com/adamdriscoll/poshtools Installer Tool: http://wixtoolset.org/ Disclaimer The sample script and documentation are provided AS IS and are not supported by the author or the author�s employer, unless otherwise agreed in writing. You bear all risk relating to the use or performance of the sample script and documentation. The author and the author�s employer disclaim all express or implied warranties (including, without limitation, any warranties of merchantability, title, infringement or fitness for a particular purpose). In no event shall the author, the author�s employer or anyone else involved in the creation, production, or delivery of the scripts be liable for any damages whatsoever arising out of the use or performance of the sample script and documentation (including, without limitation, damages for loss of business profits, business interruption, loss of business information, or other pecuniary loss), even if such person has been advised of the possibility of such damages. =========================================================================== #> <# Base requirement for Pure Storage PowerShell Toolkit 3.x is PowerShell 3.0 which provides support for the Invoke-RestMethod cmdlet. See http://technet.microsoft.com/en-us/library/hh849971.aspx for full details. #> #Requires -Version 3 #Requires -Module PureStoragePowerShellSDK # # TODO -- Add welcome message with getting started information. # # #region UNDER_DEVELOPMENT -- New-FlashArrayReport Functions <#function New-FlashArrayReport() { [CmdletBinding()] Param ( [Parameter(Mandatory=$True)][ValidateNotNullOrEmpty()][string] $Array, [Parameter()][ValidateNotNullOrEmpty()][string] $Username, [Parameter()][ValidateNotNullOrEmpty()][string] $Password) $ErrorActionPreference = "SilentlyContinue" $timestamp = Get-Date -format d #$list = $args[0] #This accepts the argument you add to your scheduled task for the list of servers. i.e. list.txt #$pfaArrays = get-content $list #grab the names of the servers/pfaArrays to check from the list.txt file. $Script:ListOfAttachments = @() $Script:Report = @() $Script:CurrentTime = Get-Date } #region Helper-functions function New-PieChart() { param([string]$FileName) [void][Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") [void][Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms.DataVisualization") $Chart = New-object System.Windows.Forms.DataVisualization.Charting.Chart $Chart.Width = 650 $Chart.Height = 370 $Chart.Left = 10 $Chart.Top = 10 $ChartArea = New-Object System.Windows.Forms.DataVisualization.Charting.ChartArea $Chart.ChartAreas.Add($ChartArea) [void]$Chart.Series.Add("Data") $legend = New-Object system.Windows.Forms.DataVisualization.Charting.Legend $legend.name = "Legend" $legend.alignment = "Center" $legend.docking = "top" $legend.bordercolor ="orange" $legend.legendstyle = "row" $chart.Legends.Add($legend) $datapoint = new-object System.Windows.Forms.DataVisualization.Charting.DataPoint(0, $capacitySpace) $datapoint.AxisLabel = "Physical Capacity" + "(" + $capacitySpace + " GB)" $Chart.Series["Data"].Points.Add($datapoint) $datapoint = new-object System.Windows.Forms.DataVisualization.Charting.DataPoint(0, $snapSpace) $datapoint.AxisLabel = "SnapShots" + "(" + $snapSpace + " GB)" $Chart.Series["Data"].Points.Add($datapoint) $datapoint = new-object System.Windows.Forms.DataVisualization.Charting.DataPoint(0, $volumeSpace) $datapoint.AxisLabel = "Volumes" + "(" + $volumeSpace + " GB)" $Chart.Series["Data"].Points.Add($datapoint) $Chart.Series["Data"].ChartType = [System.Windows.Forms.DataVisualization.Charting.SeriesChartType]::Pie $Chart.Series["Data"]["PieLabelStyle"] = "Outside" $Chart.Series["Data"]["PieLineColor"] = "Orange" $Chart.Series["Data"]["PieDrawingStyle"] = "Concave" ($Chart.Series["Data"].Points.FindMaxByValue())["Exploded"] = $true $Title = new-object System.Windows.Forms.DataVisualization.Charting.Title $Chart.Titles.Add($Title) $Chart.Titles[0].Text = "Capacity Usage Chart (Capacity/Volumes/SnapShots)" $Chart.SaveImage($FileName + ".png","png") } function Convert-Size { { [CmdletBinding()] Param ( [validateset("Bytes","KB","MB","GB","TB")] [string]$From, [validateset("Bytes","KB","MB","GB","TB")] [string]$To, [Parameter(Mandatory=$true)] [double]$Value, [int]$Precision = 4) switch($From) { "Bytes" {$value = $Value } "KB" {$value = $Value * 1024 } "MB" {$value = $Value * 1024 * 1024} "GB" {$value = $Value * 1024 * 1024 * 1024} "TB" {$value = $Value * 1024 * 1024 * 1024 * 1024} } switch ($To) { "Bytes" {return $value} "KB" {$Value = $Value/1KB} "MB" {$Value = $Value/1MB} "GB" {$Value = $Value/1GB} "TB" {$Value = $Value/1TB} } return [Math]::Round($value,$Precision,[MidPointRounding]::AwayFromZero) } function Get-Volumes { $Script:numVols = $MyVol.length $Script:startVol = 0 $Script:provisioned = 0 while ($startVol -le $numVols) { if ($MyVol[$startVol].name){ $printVol = $MyVol[$startVol].name $VolSize=$MyVol[$startVol].size/1GB $Script:provisioned = ($provisioned + $VolSize) $Script:VolumeInfo += "<tr><td>$printVol</td> <td>$volSize</td></tr>" $startVol++ }else { Break } $endVol = $endVol + 1 } $Script:provisioned = Convert-Size -From GB -To TB $provisioned -Precision 2 } function Get-Snapshots { # Note SnapShots start at 0 not 1 $Script:numSnaps = $MyPfaSnaps.length $Script:startSnap = 0 $Script:provisioned = 0 while ($startSnap -le $numSnaps) { if ($MyPfaSnaps[$startSnap].name){ $printSnap = $MyPfaSnaps[$startSnap].name $SnapSize=$MyPfaSnaps[$startSnap].size/1GB $Script:provisioned = ($provisioned + $SnapSize) $Script:SnapInfo += "<tr><td>$printSnap</td> <td>$SnapSize</td></tr>" $startSnap++ }else { Break # If there are no volumes to report, then exit the loop } $endSnap = $endSnap + 1 } $Script:provisioned = Convert-Size -From GB -To TB $provisioned -Precision 2 } #endregion function Connect-PfaFlashArray () { try { write-output "Importing data from live system - $FlashArray, please wait........" $Creds = Get-Credential $FlashArray = New-PfaArray -EndPoint $FlashArray -Credentials $Creds -IgnoreCertificateError # OLD--$MyToken = Get-PfaAPIToken -FlashArray $FlashArray -Username $Username -Password $Password -RestAPI "1.2" -ErrorAction Stop # OLD--$MySession = Connect-PfaController -FlashArray $FlashArray -API_Token $MyToken.api_token -ErrorAction Stop $MyPfaSpace = Get-PfaArraySpaceMetrics -Array $FlashArray # OLD--$MyPfaSpace = Get-PfaSpace -FlashArray $FlashArray -Session $MySession -ErrorAction Stop ######$MyPfaArray = Get-PfaConfiguration -FlashArray $FlashArray -Session $MySession -ErrorAction Stop # OLD--$MyPfaSpace = Get-PfaSpace -FlashArray $FlashArray -Session $MySession -ErrorAction Stop $MyPfaConfig = Get-PfaArrayAttributes -Array $FlashArray # OLD--$MyPfaConfig = Get-PfaConfiguration -FlashArray $FlashArray -Session $MySession -ErrorAction Stop $MyPfaVolumes = Get-PfaVolumes -Array $FlashArray #OLD--$MyPfaVolumes = Get-PfaVolumes -FlashArray $FlashArray -Session $MySession -ErrorAction Stop $MyPfaSnaps = Get-PfaAllVolumeSnapshots -Array $FlashArray # OLD--$MyPfaSnaps = Get-PfaSnapShots -FlashArray $FlashArray -Session $MySession -ErrorAction Stop Write-Host "Snapshots" -ForegroundColor DarkBlue Write-Host $MyPfaSnaps[0] Write-Host "..........................." -ForegroundColor DarkBlue Disconnect-PfaArray -Array $FlashArray # OLD--Disconnect-PfaController -FlashArray $FlashArray -Session $MySession -ErrorAction Stop #region Array Variables $Script:hostname = $MyPfaSpace.hostname $Script:capacitySpace = Convert-Size -From Bytes -To GB $MyPfaSpace.capacity -Precision 2 $Script:snapSpace = Convert-Size -From Bytes -To GB $MyPfaSpace.snapshots -Precision 2 $Script:volumeSpace = Convert-Size -From Bytes -To GB $MyPfaSpace.volumes -Precision 2 $Script:data_reduction = Convert-Size -From Bytes -To GB $MyPfaSpace.data_reduction -Precision 2 $Script:totalSpace = Convert-Size -From Bytes -To GB $MyPfaSpace.total -Precision 2 $Script:shared_space = Convert-Size -From Bytes -To GB $MyPfaSpace.shared_space -Precision 0 $Script:thin_prov = Convert-Size -From Bytes -To GB $MyPfaSpace.thin_provisioning -Precision 2 $Script:myVol = $MyPfaVolumes $Script:total_reduction = [system.Math]::Round($MyPfaSpace.total_reduction,1) #Endregion Write-Host "............................................................." -ForegroundColor DarkRed Write-Host "Total = $totalSpace (TB)" Write-Host "Total Capacity = $capacitySpace (GB)" Write-Host "Total Volumes = $volumeSpace (TB)" Write-Host "Total Snapshots = $SnapSpace (GB)" Write-Host "Total DataReduction = $data_reduction (GB)" Write-Host "Total Reduction = $total_reduction :1 " # Create the chart using our Chart function Create-PieChart -FileName ((Get-Location).Path + "\chart-$hostname") $capacitySpace, $volumeSpace, $SnapSpace $Script:ListOfAttachments += "chart-$hostname.png" Get-Volumes Get-SnapShots } # End Try catch [system.exception] { write-output "Error:"+$($_.Exception.Message) write-host $Error[0] } # End Catch } $ListOfAttachments = ((Get-Location).Path + "\" + $listofattachments) Connect-PfaFlashArray # Assemble the HTML Header and CSS for our Report $HTMLHeader = @" <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd"> <html><head><title>$hostname Capacity Report</title> <style type="text/css"> <!-- body { font-family: Verdana, Geneva, Arial, Helvetica, sans-serif; } #report { width: 900px; } table{ border-collapse: collapse; border: none; font: 10pt Verdana, Geneva, Arial, Helvetica, sans-serif; color: black; margin-bottom: 10px; } table td{ font-size: 12px; padding-left: 0px; padding-right: 20px; text-align: left; } table th { font-size: 12px; font-weight: bold; padding-left: 0px; padding-right: 20px; text-align: left; } h2{ clear: both; font-size: 130%; } h3{ clear: both; font-size: 115%; margin-left: 20px; margin-top: 30px; } p{ margin-left: 20px; font-size: 12px; } hr{ background-color: orange } table.list{ float: left; } table.list td:nth-child(1){ font-weight: bold; border-right: 1px grey solid; text-align: right; } table.list td:nth-child(2){ padding-left: 7px; } table tr:nth-child(even) td:nth-child(even){ background: #CCCCCC; } table tr:nth-child(odd) td:nth-child(odd){ background: #F2F2F2; } table tr:nth-child(even) td:nth-child(odd){ background: #DDDDDD; } table tr:nth-child(odd) td:nth-child(even){ background: #E5E5E5; } div.column { width: 400px; float: left; } div.first{ padding-right: 20px; border-right: 1px grey solid; } div.second{ margin-left: 30px; } table{ margin-left: 20px; } --> </style> </head> <body> "@ # Create HTML Report for the current System being looped through $CurrentSystemHTML = @" <hr noshade size=3 width="100%"> <div id="report"> <p><h2>$hostname Capacity Report</p></h2> <h3>System Info</h3> <img src="chart-$hostname.png"> <table class="list"> <tr> <td>Total Capacity (GB)</td> <td>$capacitySpace</td> </tr> <tr> <td>Total Snapshots (GB)</td> <td>$snapspace</td> </tr> <tr> <td>Total Volumes (GB)</td> <td>$volumespace</td> </tr> <tr> <td>Data Reduction</td> <td>$total_reduction :1</td> </tr> <tr> <td>Provisioned (TB)</td> <td>$provisioned</td> </tr> </table> <h3>Volume Info</h3> <p>Volumes(s) and sizes (GB) listed below.</p> <table class="list">$VolumeInfo</table> <h3>SnapShot Info</h3> <p>SnapShot(s) and sizes (GB) listed below.</p> <table class="list">$SnapInfo</table> <br></br> "@ # Add the current System HTML Report into the final HTML Report body $HTMLMiddle += $CurrentSystemHTML # Assemble the closing HTML for our report. $HTMLEnd = @" </div> <hr noshade size=3 width="100%"> </body> </html> "@ # Assemble the final report from all our HTML sections $HTMLmessage = $HTMLHeader + $HTMLMiddle + $HTMLEnd # Save the report out to a file in the current path $HTMLmessage | Out-File ((Get-Location).Path + "\report.html") # Email our report out $EmailFrom = "someone@somewhere.com" $EmailTo = "someone@somewhere.com" $Subject = "Pure Storage Capacity Report For $hostname" $Body = "Capacity notification email from $hostname .." $SMTPServer = "your.domain.com" $SMTPClient = New-Object Net.Mail.SmtpClient($SmtpServer, 587) $SMTPClient.EnableSsl = $true $SMTPClient.Credentials = New-Object System.Net.NetworkCredential("username", "password"); $emailMessage = New-Object System.Net.Mail.MailMessage $emailMessage.From = $EmailFrom $emailMessage.To.Add($EmailTo) $emailMessage.Subject = $Subject $emailMessage.Body = $HTMLmessage $emailMessage.IsBodyHTML = $true $attachment = New-Object System.Net.Mail.Attachment �ArgumentList $ListOfAttachments $attachment.ContentDisposition.Inline = $True $attachment.ContentDisposition.DispositionType = "Inline" $attachment.ContentType.MediaType = "image/jpg" $attachment.ContentId = 'image1.jpg' $emailMessage.Attachments.Add( $attachment ) $SMTPClient.Send($emailMessage) $attachment.Dispose(); $emailMessage.Dispose();#> #endregion #.ExternalHelp PowerShell-Toolkit.psm1-help.xml function Test-WindowsBestPractices() { <#[CmdletBinding()] Param ( [Parameter(Mandatory = $True)][string]$ComputerName )#> Clear-Host Write-Output '============================================================' Write-Output 'Pure Storage Windows Server Best Practice Analyzer' Write-Output '============================================================' <#TODO -- Add to output VERSION # VERSION # output to screeen LINK at bottom HBA CHECK WINDOWS VERSION $Windows2008R2 = @( 'KB979711', 'KB2520235', 'KB2528357', 'KB2684681', 'KB2718576', 'KB2522766', 'KB2528357', 'KB2684681', 'KB2754704', 'KB2990170') $Windows2012 = @('KB2796995', 'KB2990170') $Windows2012R2 = @('KB2990170') $Windows2016 = @('KB2967917', 'KB2961072', 'KB2998527') $HotfixIds = Get-HotFix ForEach ($Hotfix in $Windows2016) { #Write-Host $Hotfix '---' $HotfixId } #> Write-Output '' Write-Output '===================================' Write-Output 'Host Information' Write-Output '===================================' Get-SilComputer #Get-SilWindowsUpdate | Format-Table -AutoSize Write-Output '' Write-Output '===================================' Write-Output 'Multipath-IO Verificaton' Write-Output '===================================' if (!(Get-WindowsFeature -Name 'Multipath-IO').InstalledStatus -eq 'Installed') { Write-Output 'PASS: Multipath I/O is installed.' } else { Write-Warning 'FAIL: Please install Multipath-IO.' break } Write-Output '' Write-Output '===================================' Write-Output 'MPIO Setting Verification' Write-Output '===================================' $PassFail = 0 ForEach ($DSM in Get-MSDSMSupportedHW) { if (($DSM.VendorId -eq 'PURE' -and $DSM.ProductId -eq 'FlashArray')) { Write-Output 'PASS: Microsoft Device Specific Module (MSDSM) is configured for Pure Storage FlashArray.' $MPIO = Get-MPIOSetting | Out-String -Stream if (($MPIO[4] -replace ' ', '') -ceq 'PDORemovePeriod:30') { #30 Write-Output 'PASS: MPIO PDORemovePeriod passes Windows Server Best Practice check.' $PassFail = -1 } else { Write-Warning 'FAIL: MPIO PDORemovePeriod does NOT pass Windows Server Best Practice check.' $PassFail = + 1 } if (($MPIO[7] -replace ' ', '') -ceq 'UseCustomPathRecoveryTime:Enabled') { #Enabled Write-Output 'PASS: MPIO UseCustomPathRecoveryTime passes Windows Server Best Practice check.' $PassFail = -1 } else { Write-Warning 'FAIL: MPIO UseCustomPathRecoveryTime does NOT pass Windows Server Best Practice check.' } if (($MPIO[8] -replace ' ', '') -ceq 'CustomPathRecoveryTime:20') { #20 Write-Output 'PASS: MPIO CustomPathRecoveryTime passes Windows Server Best Practice check.' $PassFail = -1 } else { Write-Warning 'FAIL: MPIO CustomPathRecoveryTime does NOT pass Windows Server Best Practice check.' $PassFail = + 1 } if (($MPIO[9] -replace ' ', '') -ceq 'DiskTimeoutValue:60') { #60 Write-Output 'PASS: MPIO DiskTimeoutValue passes Windows Server Best Practice check.' $PassFail = -1 } else { Write-Warning 'FAIL: MPIO PDiskTimeoutValue does NOT pass Windows Server Best Practice check.' $PassFail = + 1 } <#RESEARCH -- Support for Windows Server 2008 R2. $Paths = (Get-ChildItem -Path "hklm:\SYSTEM\CurrentControlSet\Services\msdsm\Parameters\DsmLoadBalanceSettings").Name ForEach ($Path in $Paths) { $PureVolumePath = $Path.Substring(93) (Get-ChildItem -Path "hklm:\SYSTEM\CurrentControlSet\Services\msdsm\Parameters\DsmLoadBalanceSettings\$PureVolumePath").Name.Substring(93) | Select Name } $DsmContext = Get-WmiObject -Namespace 'root/WMI' -Class MPIO_REGISTERED_DSM $DsmCounters = Get-Wmiobject -ComputerName $ComputerName -NameSpace root/WMI -Class MPIO_TIMERS_COUNTERS Invoke-WmiMethod -Class MPIO_WMI_METHODS -Name SetDSMCounters -ArgumentList $DsmContext#, $DsmCounters Invoke-WmiMethod -Class MPIO_TIMERS_COUNTERS -Name PDORemovePeriod -ArgumentList @{PDORemovePeriod=60} Set-WmiInstance -Class MPIO_TIMERS_COUNTERS -Arguments @{PDORemovePeriod=60} Get-WmiObject -Query 'SELECT InstanceName from MPIO_WMI_METHODS' Get-WmiObject -Namespace 'root/WMI' -Class MPIO_ADAPTER_INFORMATION -ComputerName $env:COMPUTERNAME Get-WmiObject -Namespace 'root/WMI' -Query 'SELECT PathList FROM MPIO_PATH_INFORMATION' Get-WmiObject -Namespace 'root/WMI' -Query 'SELECT NumberPaths FROM MPIO_PATH_INFORMATION' -ComputerName $env:COMPUTERNAME Get-WmiObject -Namespace 'root/WMI' -Query 'SELECT DsmParameters FROM MPIO_REGISTERED_DSM' -ComputerName $env:COMPUTERNAME Get-Wmiobject -ComputerName CSG-WS2012R2-01 -NameSpace root/WMI -Class MPIO_DISK_HEALTH_INFO Get-Wmiobject -ComputerName CSG-WS2012R2-01 -NameSpace root/WMI -Class MPIO_DISK_INFO Get-Wmiobject -ComputerName CSG-WS2012R2-01 -NameSpace root/WMI -Class MPIO_PATH_HEALTH_INFO Get-Wmiobject -ComputerName CSG-WS2012R2-01 -NameSpace root/WMI -Class MPIO_PATH_INFORMATION Get-Wmiobject -ComputerName CSG-WS2012R2-01 -NameSpace root/WMI -Class MPIO_REGISTERED_DSM Get-Wmiobject -ComputerName CSG-WS2012R2-01 -NameSpace root/WMI -Class MPIO_TIMERS_COUNTERS ###### Get-WmiObject -Namespace 'root/cimv2' -Class DSM_Load_Balance_Policy #> } else { if ($DSM.VendorId -eq 'Vendor 8') { Write-Warning "RECOMMENDATION: Remove the sample DSM entry (VendorId=$DSM.VendorId and ProductId=$DSM.ProductId)" } } } Write-Output '' Write-Output '===================================' Write-Output 'TRIM/UNMAP Verification' Write-Output '===================================' if (!(Get-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Control\FileSystem' -Name 'DisableDeleteNotification') -eq 0) { Write-Output 'PASS: Delete Notification Enabled' } else { Write-Warning 'Delete Notification Disabled. Pure Storage Best Practice is to enable delete notifications.' } if ($PassFail -ge 0) { Write-Output '' Write-Output '===================================' Write-Output 'Set Windows Server Best Practices' Write-Output '===================================' Write-Output 'There are one or more recommended best practices that are not correct.' $retval = Read-Host -Prompt "Would you like to apply the recommended Windows Server best practices for Pure Storage?`n`nWARNING: This requires a reboot of the host.`n`n[Y/N]" if ($retval.ToUpper() -eq 'Y') { Set-MPIOSetting -NewPDORemovePeriod 30 -NewDiskTimeout 60 -CustomPathRecovery Enabled -NewPathRecoveryInterval 20 } else { Write-Warning -Message 'You have chosen not to apply Windows Server best practices for Pure Storage. You will be required to make the changes manually or re-run the Test-WindowsBestPractices cmdlet.' } } #TODO: iSCSI #TODO: Create Analysis report using New-Report cmdlets } #> # # UNDER DEVELOPMENT -- Optimize-Unmap <#function Optimize-Unmap() { [CmdletBinding()] Param ( [Parameter(Mandatory = $True)][string]$vCenter, [Parameter(Mandatory = $True)][string]$vCenterUser, [Parameter(Mandatory = $True)][string]$vCenterPassword, [Parameter(Mandatory = $True)][string[]]$FlashArrays, [Parameter(Mandatory = $True)][PSCredential]$Creds, [Parameter(Mandatory = $True)][string]$LogPath, [Parameter(Mandatory = $True)][string]$LogFile ) #Important PowerCLI if not done and connect to vCenter Add-PsSnapin VMware.VimAutomation.Core #Create log folder if non-existent If (!(Test-Path -Path $LogPath)) { New-Item -ItemType Directory -Path $LogPath } $LogFile = $LogPath + (Get-Date -Format o |ForEach-Object {$_ -Replace ':', '.'}) + $LogFile #Connect to FlashArray via REST $facount=0 $purevols=$null $purevol=$null $EndPoint= @() #$Pwd = ConvertTo-SecureString $pureuserpwd -AsPlainText -Force #$Creds = New-Object System.Management.Automation.PSCredential ($pureuser, $pwd) foreach ($flasharray in $flasharrays) { if ($facount -eq 0) { $EndPoint = @(New-PfaArray -EndPoint $flasharray -Credentials $Creds -IgnoreCertificateError) $purevolumes = @(Get-PfaVolumes -Array $EndPoint[$facount]) $tempvols = @(Get-PfaVolumes -Array $EndPoint[$facount]) $arraysnlist = @(@{$tempvols[0].serial.substring(0,16) = $facount}) } else { $EndPoint += New-PfaArray -EndPoint $flasharray -Credentials $Creds -IgnoreCertificateError $purevolumes += Get-PfaVolumes -Array $EndPoint[$facount] $tempvols = Get-PfaVolumes -Array $EndPoint[$facount] $arraysnlist += @{$tempvols[0].serial.substring(0,16) = $facount} } $facount = $facount + 1 } add-content $LogFile 'Connected to FlashArray:' add-content $LogFile $purevip add-content $LogFile '----------------' #Set-PowerCLIConfiguration -invalidcertificateaction 'ignore' -confirm:$false |out-null #Set-PowerCLIConfiguration -Scope Session -WebOperationTimeoutSeconds -1 -confirm:$false |out-null connect-viserver -Server $vCenter -username $vCenterUser -password $vCenterPassword | Out-Null add-content $LogFile 'Connected to vCenter:' add-content $LogFile $vCenter add-content $LogFile '----------------' #Gather VMFS Datastores and identify how many are Pure Storage volumes $datastores = get-datastore add-content $LogFile 'Found the following datastores:' add-content $LogFile $datastores add-content $LogFile '***************' #Starting UNMAP Process on datastores $volcount=0 $purevol = $null foreach ($datastore in $datastores) { $esx = $datastore | get-vmhost | where-object {($_.version -like '5.5.*') -or ($_.version -like '6.0.*')} | Select-Object -last 1 if ($datastore.Type -ne 'VMFS') { add-content $LogFile 'This volume is not a VMFS volume and cannot be reclaimed. Skipping...' add-content $LogFile $datastore.Type } else { $lun = get-scsilun -datastore $datastore | select-object -last 1 $esxcli=get-esxcli -VMHost $esx add-content $LogFile 'The following datastore is being examined:' add-content $LogFile $datastore add-content $LogFile 'The following ESXi is the chosen source:' add-content $LogFile $esx if ($lun.canonicalname -like 'naa.624a9370*') { $volserial = ($lun.CanonicalName.ToUpper()).substring(12) $purevol = $purevolumes | where-object { $_.serial -eq $volserial } $arraychoice = $arraysnlist.($volserial.substring(0,16)) $volinfo = Get-PfaVolumeSpaceMetrics -Array $EndPoint[$arraychoice] -VolumeName $purevol.name $volreduction = '{0:N3}' -f ($volinfo.data_reduction) $volphysicalcapacity = '{0:N3}' -f ($volinfo.volumes/1024/1024/1024) add-content $LogFile 'This datastore is a Pure Storage Volume.' add-content $LogFile $lun.CanonicalName add-content $LogFile 'The current data reduction for this volume prior to UNMAP is:' add-content $LogFile $volreduction add-content $LogFile 'The current physical space consumption in GB of this device prior to UNMAP is:' add-content $LogFile $volphysicalcapacity $blockcount = [math]::floor($datastore.FreeSpaceMB * .01) add-content $LogFile 'The maximum allowed block count for this datastore is' add-content $LogFile $blockcount $esxcli.storage.vmfs.unmap($blockcount, $datastore.Name, $null) |out-null Start-Sleep -s 10 $volinfo = Get-PfaVolumeSpaceMetrics -Array $EndPoint[$arraychoice] -VolumeName $purevol.name $volreduction = '{0:N3}' -f ($volinfo.data_reduction) $volphysicalcapacitynew = '{0:N3}' -f ($volinfo.volumes/1024/1024/1024) $unmapsavings = ($volphysicalcapacity - $volphysicalcapacitynew) $volcount=$volcount+1 add-content $LogFile 'The new data reduction for this volume after UNMAP is:' add-content $LogFile $volreduction add-content $LogFile 'The new physical space consumption in GB of this device after UNMAP is:' add-content $LogFile $volphysicalcapacitynew add-content $LogFile 'The following capacity in GB has been reclaimed from the FlashArray from this volume:' add-content $LogFile $unmapsavings add-content $LogFile '---------------------' Start-Sleep -s 5 } else { add-content $LogFile 'This datastore is NOT a Pure Storage Volume. Skipping...' add-content $LogFile $lun.CanonicalName add-content $LogFile '---------------------' } } } #disconnecting sessions $facount=0 foreach ($flasharray in $flasharrays) { Disconnect-PfaArray -Array $flasharray $facount = $facount + 1 } } #> #endregion #region Miscellenaous-Cmdlets #.ExternalHelp PowerShell-Toolkit.psm1-help.xml function Get-WindowsPowerScheme() { [CmdletBinding()] Param ( [Parameter(Mandatory = $True)] [ValidateNotNullOrEmpty()] [string]$ComputerName ) try { $PowerScheme = Get-WmiObject -Class WIN32_PowerPlan -Namespace 'root\cimv2\power' -ComputerName $ComputerName -Filter "isActive='true'" Write-Warning $ComputerName 'is set to' $PowerScheme.ElementName } catch { } } #.ExternalHelp PowerShell-Toolkit.psm1-help.xml function Open-PureStorageGitHub { try { $link = 'https://github.com/purestorage-openconnect/powershell-toolkit' $browserProcess = [System.Diagnostics.Process]::Start($link) } catch { } } #.ExternalHelp PowerShell-Toolkit.psm1-help.xml function Set-QueueDepth() { [CmdletBinding()] Param ( [Parameter(Mandatory = $True)][ValidateNotNullOrEmpty()][int] $Qd ) try { $DriverParam = Get-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Services\ql2300\Parameters\Device\' If (!$DriverParam.DriverParameter) { $Confirm = Read-Host 'The Queue Depth setting for the QLogic Driver (ql2300.sys) does not exist would you like to create it? Y/N' switch ($Confirm) { 'Y' { Set-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Services\ql2300\Parameters\Device' -Name 'DriverParameter' -Value "qd=$Qd" } 'N' { } } } Else { $CurrentQD = $DriverParam.DriverParameter $Confirm = Read-Host "QLogic Driver Queue Depth is $CurrentQD. Do you want to update to $Qd ? Y/N" switch ($Confirm) { 'Y' { Set-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Services\ql2300\Parameters\Device' -Name 'DriverParameter' -Value "qd=$Qd" } 'N' { } } } } catch { } } #.ExternalHelp PowerShell-Toolkit.psm1-help.xml function Get-QuickFixEngineering { Get-WmiObject -Class Win32_QuickFixEngineering | Select-Object -Property Description, HotFixID, InstalledOn | Format-Table -Wrap } #.ExternalHelp PowerShell-Toolkit.psm1-help.xml function Get-QueueDepth() { try { $DriverParam = Get-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Services\ql2300\Parameters\Device\' 'Queue Depth is ' + $DriverParam.DriverParameter } catch { } } #.ExternalHelp PowerShell-Toolkit.psm1-help.xml function Get-HostBusAdapter() { [CmdletBinding()] Param ( [Parameter(Mandatory = $true)] [string] $ComputerName ) try { $port = Get-WmiObject -Class MSFC_FibrePortHBAAttributes -Namespace 'root\WMI' -ComputerName $ComputerName $hbas = Get-WmiObject -Class MSFC_FCAdapterHBAAttributes -Namespace 'root\WMI' -ComputerName $ComputerName $hbaProp = $hbas | Get-Member -MemberType Property, AliasProperty | Select-Object -ExpandProperty name | Where-Object { $_ -notlike '__*' } $hbas = $hbas | Select-Object $hbaProp $hbas | %{ $_.NodeWWN = ((($_.NodeWWN) | % { '{0:x2}' -f $_ }) -join ':').ToUpper() } ForEach ($hba in $hbas) { Add-Member -MemberType NoteProperty -InputObject $hba -Name FabricName -Value (($port | Where-Object { $_.instancename -eq $hba.instancename }).attributes | Select-Object @{ Name = 'Fabric Name'; Expression = { (($_.fabricname | % { '{0:x2}' -f $_ }) -join ':').ToUpper() } }, @{ Name = 'Port WWN'; Expression = { (($_.PortWWN | % { '{0:x2}' -f $_ }) -join ':').ToUpper() } }) -passThru } } catch { } } #.ExternalHelp PowerShell-Toolkit.psm1-help.xml function Register-HostVolumes () { [CmdletBinding()] Param ( [Parameter(Mandatory = $True)] [string]$Computername ) $cmds = "`"RESCAN`"" $scriptblock = [string]::Join(',', $cmds) $diskpart = $ExecutionContext.InvokeCommand.NewScriptBlock("$scriptblock | DISKPART") $result = Invoke-Command -ComputerName $Computername -ScriptBlock $diskpart $disks = Invoke-Command -Computername $Computername { Get-Disk } $i = 0 ForEach ($disk in $disks) { If ($disk.FriendlyName -like 'PURE FlashArray*') { If ($disk.OperationalStatus -ne 1) { $disknumber = $disk.Number $cmds = "`"SELECT DISK $disknumber`"", "`"ATTRIBUTES DISK CLEAR READONLY`"", "`"ONLINE DISK`"" $scriptblock = [string]::Join(',', $cmds) $diskpart = $ExecutionContext.InvokeCommand.NewScriptBlock("$scriptblock | DISKPART") $result = Invoke-Command -ComputerName $Computername -ScriptBlock $diskpart -ErrorAction Stop } } } } #.ExternalHelp PowerShell-Toolkit.psm1-help.xml function Unregister-HostVolumes () { [CmdletBinding()] Param ( [Parameter(Mandatory = $True)] [string]$Computername ) $cmds = "`"RESCAN`"" $scriptblock = [string]::Join(',', $cmds) $diskpart = $ExecutionContext.InvokeCommand.NewScriptBlock("$scriptblock | DISKPART") $result = Invoke-Command -ComputerName $Computername -ScriptBlock $diskpart $disks = Invoke-Command -Computername $Computername { Get-Disk } $i = 0 ForEach ($disk in $disks) { If ($disk.FriendlyName -like 'PURE FlashArray*') { If ($disk.OperationalStatus -ne 1) { $disknumber = $disk.Number $cmds = "`"SELECT DISK $disknumber`"", "`"OFFLINE DISK`"" $scriptblock = [string]::Join(',', $cmds) $diskpart = $ExecutionContext.InvokeCommand.NewScriptBlock("$scriptblock | DISKPART") $result = Invoke-Command -ComputerName $Computername -ScriptBlock $diskpart -ErrorAction Stop } } } } #endregion #region PureStorage-VSS-Cmdlets #.ExternalHelp PowerShell-Toolkit.psm1-help.xml function Get-ShadowCopy() { [CmdletBinding()] Param ( [Parameter(Mandatory = $True)][string]$ScriptName = 'PUREVSS-SNAP', [Parameter(Mandatory = $True)][string]$MetadataFile, [Parameter(Mandatory = $True)][string]$ShadowCopyAlias, [Parameter(Mandatory = $True)][string]$ExposeAs ) $dsh = "./$ScriptName.PFA" 'RESET', "LOAD METADATA $MetadataFile.cab", 'IMPORT', "EXPOSE %$ShadowCopyAlias% $ExposeAs", 'EXIT' | Set-Content $dsh DISKSHADOW /s $dsh Remove-Item $dsh } #.ExternalHelp PowerShell-Toolkit.psm1-help.xml function New-ShadowCopy() { [CmdletBinding()] Param ( [Parameter(Mandatory = $True)][string[]]$Volume, [Parameter(Mandatory = $True)][string]$ScriptName = 'PUREVSS-SNAP', [Parameter(Mandatory = $True)][string]$MetadataFile, [Parameter(Mandatory = $True)][string]$ShadowCopyAlias, [ValidateSet('On', 'Off')][string]$VerboseMode = 'On' ) $dsh = "./$ScriptName.PFA" foreach ($Vol in $Volume) { "ADD VOLUME $Vol ALIAS $ShadowCopyAlias PROVIDER {781c006a-5829-4a25-81e3-d5e43bd005ab}" } 'RESET', 'SET CONTEXT PERSISTENT', 'SET OPTION TRANSPORTABLE', "SET METADATA $MetadataFile.cab", "SET VERBOSE $VerboseMode", 'BEGIN BACKUP', "ADD VOLUME $Volume ALIAS $ShadowCopyAlias PROVIDER {781c006a-5829-4a25-81e3-d5e43bd005ab}", 'CREATE', 'END BACKUP' | Set-Content $dsh DISKSHADOW /s $dsh Remove-Item $dsh } #endregion #region PureStoragePowerShellSDK-Cmdlets #.ExternalHelp PowerShell-Toolkit.psm1-help.xml function Get-BlockSize () { } #endregion #Export-ModuleMember -function Optimize-Unmap Export-ModuleMember -function Get-WindowsPowerScheme Export-ModuleMember -function Get-HostBusAdapter Export-ModuleMember -function Register-HostVolumes Export-ModuleMember -function Unregister-HostVolumes Export-ModuleMember -function Get-QuickFixEngineering Export-ModuleMember -function Test-WindowsBestPractices #Export-ModuleMember -function Get-BlockSize Export-ModuleMember -function Get-HostBusAdapter Export-ModuleMember -function Set-QueueDepth Export-ModuleMember -function New-ShadowCopy Export-ModuleMember -function Get-ShadowCopy #Export-ModuleMember -function New-FlashArraySpaceReport |