├── IdentifyDeadSpace.ps1 ├── LICENSE ├── LogInsightIngestionRESTexample.ps1 ├── PureStorageVMwareStorageTool.ps1 ├── README.md ├── SnapshotOneOrMoreVMFSandResignature.ps1 ├── VMwareFlashArraySATPCleanup.ps1 ├── bestpracticechecker.ps1 ├── bestpractices.ps1 ├── checkandfixUNMAP.ps1 ├── createVVolVMDKsforSRM.ps1 ├── createhostgroups.ps1 ├── disallowsnapshotlunfailover.ps1 ├── findFlashArrayVolumeNamefromVMFSName.ps1 ├── findforcemountedVMFS.ps1 ├── findsrmVMs.ps1 ├── getScIDfromArrayID.ps1 ├── presentVMFScopy.ps1 ├── protectiongrouprecovery.ps1 ├── unattendedUnmapConfigurator.ps1 ├── unmapsdk.ps1 ├── unmapsdkPowerActions.ps1 └── unmapsdkunattended.ps1 /IdentifyDeadSpace.ps1: -------------------------------------------------------------------------------- 1 | #*************************************************************************************************** 2 | #VMWARE POWERCLI AND PURE STORAGE POWERSHELL SDK MUST BE INSTALLED ON THE MACHINE THIS IS RUNNING ON 3 | #*************************************************************************************************** 4 | # 5 | #For info, refer to www.codyhosterman.com 6 | # 7 | #***************************************************************** 8 | #Threshold is in GB. This will return only datastores with that number of GB of virtual dead space or more 9 | #***************************************************************** 10 | 11 | <# 12 | *******Disclaimer:****************************************************** 13 | This scripts are offered "as is" with no warranty. While this 14 | scripts is tested and working in my environment, it is recommended that you test 15 | this script in a test lab before using in a production environment. Everyone can 16 | use the scripts/commands provided here without any written permission but I 17 | will not be liable for any damage or loss to the system. 18 | ************************************************************************ 19 | 20 | This script will identify VMware VMFS volumes on Pure Storage FlashArray volumes that have a certain amount of virtual dead space and return those datastores. 21 | 22 | Version 1.2 23 | 24 | This can be run directly from PowerCLI or from a standard PowerShell prompt. PowerCLI and the FlashArray PowerShell SDK must be installed on the local host regardless. 25 | 26 | Supports: 27 | -PowerShell 3.0 or later 28 | -Pure Storage PowerShell SDK 1.7 or later 29 | -PowerCLI 6.3 Release 1 and later 30 | -Purity 4.1 and later 31 | -FlashArray 400 Series and //m 32 | #> 33 | #Create log folder if non-existent 34 | write-host "Please choose a directory to store the script log" 35 | function ChooseFolder([string]$Message, [string]$InitialDirectory) 36 | { 37 | $app = New-Object -ComObject Shell.Application 38 | $folder = $app.BrowseForFolder(0, $Message, 0, $InitialDirectory) 39 | $selectedDirectory = $folder.Self.Path 40 | return $selectedDirectory 41 | } 42 | $logfolder = ChooseFolder -Message "Please select a log file directory" -InitialDirectory 'MyComputer' 43 | $logfile = $logfolder + '\' + (Get-Date -Format o |ForEach-Object {$_ -Replace ':', '.'}) + "deadspace.txt" 44 | write-host "Script result log can be found at $logfile" -ForegroundColor Green 45 | #Import PowerCLI. Requires PowerCLI version 6.3 or later. Will fail here if PowerCLI is not installed 46 | #Will try to install PowerCLI with PowerShellGet if PowerCLI is not present. 47 | if ((!(Get-Module -Name VMware.VimAutomation.Core -ErrorAction SilentlyContinue)) -and (!(get-Module -Name VMware.PowerCLI -ListAvailable))) { 48 | if (Test-Path “C:\Program Files (x86)\VMware\Infrastructure\PowerCLI\Scripts\Initialize-PowerCLIEnvironment.ps1”) 49 | { 50 | . “C:\Program Files (x86)\VMware\Infrastructure\PowerCLI\Scripts\Initialize-PowerCLIEnvironment.ps1” |out-null 51 | } 52 | elseif (Test-Path “C:\Program Files (x86)\VMware\Infrastructure\vSphere PowerCLI\Scripts\Initialize-PowerCLIEnvironment.ps1”) 53 | { 54 | . “C:\Program Files (x86)\VMware\Infrastructure\vSphere PowerCLI\Scripts\Initialize-PowerCLIEnvironment.ps1” |out-null 55 | } 56 | elseif (!(get-Module -Name VMware.PowerCLI -ListAvailable)) 57 | { 58 | if (get-Module -name PowerShellGet -ListAvailable) 59 | { 60 | try 61 | { 62 | Get-PackageProvider -name NuGet -ListAvailable -ErrorAction stop 63 | } 64 | catch 65 | { 66 | Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force -Scope CurrentUser -Confirm:$false 67 | } 68 | Install-Module -Name VMware.PowerCLI –Scope CurrentUser -Confirm:$false -Force 69 | } 70 | else 71 | { 72 | write-host ("PowerCLI could not automatically be installed because PowerShellGet is not present. Please install PowerShellGet or PowerCLI") -BackgroundColor Red 73 | write-host "PowerShellGet can be found here https://www.microsoft.com/en-us/download/details.aspx?id=51451 or is included with PowerShell version 5" 74 | write-host "Terminating Script" -BackgroundColor Red 75 | return 76 | } 77 | } 78 | if ((!(Get-Module -Name VMware.VimAutomation.Core -ErrorAction SilentlyContinue)) -and (!(get-Module -Name VMware.PowerCLI -ListAvailable))) 79 | { 80 | write-host ("PowerCLI not found. Please verify installation and retry.") -BackgroundColor Red 81 | write-host "Terminating Script" -BackgroundColor Red 82 | return 83 | } 84 | } 85 | set-powercliconfiguration -invalidcertificateaction "ignore" -confirm:$false |out-null 86 | if ( !(Get-Module -ListAvailable -Name PureStoragePowerShellSDK -ErrorAction SilentlyContinue) ) { 87 | write-host ("FlashArray PowerShell SDK not found. Please verify installation and retry.") -BackgroundColor Red 88 | write-host "Get it here: https://github.com/PureStorage-Connect/PowerShellSDK" 89 | write-host "Terminating Script" -BackgroundColor Red 90 | add-content $logfile ("FlashArray PowerShell SDK not found. Please verify installation and retry.") 91 | add-content $logfile "Get it here: https://github.com/PureStorage-Connect/PowerShellSDK" 92 | add-content $logfile "Terminating Script" 93 | return 94 | } 95 | write-host ' __________________________' 96 | write-host ' /++++++++++++++++++++++++++\' 97 | write-host ' /++++++++++++++++++++++++++++\' 98 | write-host ' /++++++++++++++++++++++++++++++\' 99 | write-host ' /++++++++++++++++++++++++++++++++\' 100 | write-host ' /++++++++++++++++++++++++++++++++++\' 101 | write-host ' /++++++++++++/----------\++++++++++++\' 102 | write-host ' /++++++++++++/ \++++++++++++\' 103 | write-host ' /++++++++++++/ \++++++++++++\' 104 | write-host ' /++++++++++++/ \++++++++++++\' 105 | write-host ' /++++++++++++/ \++++++++++++\' 106 | write-host ' \++++++++++++\ /++++++++++++/' 107 | write-host ' \++++++++++++\ /++++++++++++/' 108 | write-host ' \++++++++++++\ /++++++++++++/' 109 | write-host ' \++++++++++++\ /++++++++++++/' 110 | write-host ' \++++++++++++\ /++++++++++++/' 111 | write-host ' \++++++++++++\' 112 | write-host ' \++++++++++++\' 113 | write-host ' \++++++++++++\' 114 | write-host ' \++++++++++++\' 115 | write-host ' \------------\' 116 | write-host 'Pure Storage VMware ESXi Dead Space Detection Script v1.1' 117 | write-host '----------------------------------------------------------------------------------------------------' 118 | 119 | $FAcount = 0 120 | $inputOK = $false 121 | do 122 | { 123 | try 124 | { 125 | [int]$FAcount = Read-Host "How many FlashArrays do you want to search? (enter a number)" 126 | $inputOK = $true 127 | } 128 | catch 129 | { 130 | Write-Host -ForegroundColor red "INVALID INPUT! Please enter a numeric value." 131 | } 132 | } 133 | until ($inputOK) 134 | $flasharrays = @() 135 | for ($i=0;$i -lt $FAcount;$i++) 136 | { 137 | $flasharray = read-host "Please enter a FlashArray IP or FQDN" 138 | $flasharrays += $flasharray 139 | } 140 | $Creds = $Host.ui.PromptForCredential("FlashArray Credentials", "Please enter your FlashArray username and password.", "","") 141 | #Connect to FlashArray via REST 142 | $facount=0 143 | $purevolumes=@() 144 | $purevol=$null 145 | $EndPoint= @() 146 | 147 | <# 148 | Connect to FlashArray via REST with the SDK 149 | Creates an array of connections for as many FlashArrays as you have entered into the $flasharrays variable. 150 | Assumes the same credentials are in use for every FlashArray 151 | #> 152 | 153 | foreach ($flasharray in $flasharrays) 154 | { 155 | if ($facount -eq 0) 156 | { 157 | try 158 | { 159 | $EndPoint += (New-PfaArray -EndPoint $flasharray -Credentials $Creds -IgnoreCertificateError -ErrorAction stop) 160 | } 161 | catch 162 | { 163 | write-host ("Connection to FlashArray " + $flasharray + " failed. Please check credentials or IP/FQDN") -BackgroundColor Red 164 | write-host $Error[0] 165 | write-host "Terminating Script" -BackgroundColor Red 166 | add-content $logfile ("Connection to FlashArray " + $flasharray + " failed. Please check credentials or IP/FQDN") 167 | add-content $logfile $Error[0] 168 | add-content $logfile "Terminating Script" 169 | return 170 | } 171 | $purevolumes += Get-PfaVolumes -Array $EndPoint[$facount] 172 | $tempvols = @(Get-PfaVolumes -Array $EndPoint[$facount]) 173 | $arraysnlist = @($tempvols.serial[0].substring(0,16)) 174 | } 175 | else 176 | { 177 | try 178 | { 179 | $EndPoint += New-PfaArray -EndPoint $flasharray -Credentials $Creds -IgnoreCertificateError 180 | $purevolumes += Get-PfaVolumes -Array $EndPoint[$facount] 181 | $tempvols = Get-PfaVolumes -Array $EndPoint[$facount] 182 | $arraysnlist += $tempvols.serial[0].substring(0,16) 183 | } 184 | catch 185 | { 186 | write-host ("Connection to FlashArray " + $flasharray + " failed. Please check credentials or IP/FQDN") -BackgroundColor Red 187 | write-host $Error[0] 188 | add-content $logfile ("Connection to FlashArray " + $flasharray + " failed. Please check credentials or IP/FQDN") 189 | add-content $logfile $Error[0] 190 | return 191 | } 192 | } 193 | $facount = $facount + 1 194 | } 195 | 196 | add-content $logfile 'Connected to the following FlashArray(s):' 197 | add-content $logfile $flasharrays 198 | add-content $logfile '----------------------------------------------------------------------------------------------------' 199 | write-host "" 200 | 201 | $vcenter = read-host "Please enter a vCenter IP or FQDN" 202 | $newcreds = Read-host "Re-use the FlashArray credentials for vCenter? (y/n)" 203 | while (($newcreds -ine "y") -and ($newcreds -ine "n")) 204 | { 205 | write-host "Invalid entry, please enter y or n" 206 | $newcreds = Read-host "Re-use the FlashArray credentials for vCenter? (y/n)" 207 | } 208 | if ($newcreds -ieq "n") 209 | { 210 | $Creds = $Host.ui.PromptForCredential("vCenter Credentials", "Please enter your vCenter username and password.", "","") 211 | } 212 | try 213 | { 214 | connect-viserver -Server $vcenter -Credential $Creds -ErrorAction Stop |out-null 215 | add-content $logfile ('Connected to vCenter at ' + $vcenter) 216 | add-content $logfile '----------------------------------------------------------------------------------------------------' 217 | } 218 | catch 219 | { 220 | write-host "Failed to connect to vCenter" -BackgroundColor Red 221 | write-host $vcenter 222 | write-host $Error[0] 223 | write-host "Terminating Script" -BackgroundColor Red 224 | add-content $logfile "Failed to connect to vCenter" 225 | add-content $logfile $vcenter 226 | add-content $logfile $Error[0] 227 | add-content $logfile "Terminating Script" 228 | return 229 | } 230 | write-host "" 231 | $inputOK = $false 232 | do 233 | { 234 | try 235 | { 236 | [int]$unmapthreshold = Read-Host "What virtual dead space threshold in GB do you want to limit the results to? (enter a number)" 237 | $inputOK = $true 238 | } 239 | catch 240 | { 241 | Write-Host -ForegroundColor red "INVALID INPUT! Please enter a numeric value." 242 | } 243 | } 244 | until ($inputOK) 245 | write-host "" 246 | write-host "" 247 | $datastorestounmap =@() 248 | $totaldeadspace = 0 249 | $datastores = get-datastore 250 | foreach ($datastore in $datastores) 251 | { 252 | add-content $logfile (get-date) 253 | add-content $logfile ('The datastore named ' + $datastore + ' is being examined') 254 | if ($datastore.Type -ne 'VMFS') 255 | { 256 | add-content $logfile ('This volume is not a VMFS volume, it is of type ' + $datastore.Type + ' and cannot be reclaimed. Skipping...') 257 | add-content $logfile '' 258 | add-content $logfile '----------------------------------------------------------------------------------------------------' 259 | } 260 | else 261 | { 262 | 263 | $lun = $datastore.ExtensionData.Info.Vmfs.Extent.DiskName |select-object -unique 264 | if ($lun.count -eq 1) 265 | { 266 | add-content $logfile "The UUID for this volume is:" 267 | add-content $logfile ($datastore.ExtensionData.Info.Vmfs.Extent.DiskName) 268 | if ($lun -like 'naa.624a9370*') 269 | { 270 | $volserial = ($lun.ToUpper()).substring(12) 271 | $purevol = $purevolumes | where-object { $_.serial -eq $volserial } 272 | if ($purevol.name -eq $null) 273 | { 274 | add-content $logfile 'ERROR: This volume has not been found. Please make sure that all of the FlashArrays presented to this vCenter are entered into this script.' 275 | add-content $logfile '' 276 | add-content $logfile '----------------------------------------------------------------------------------------------------' 277 | 278 | } 279 | else 280 | { 281 | for($i=0; $i -lt $arraysnlist.count; $i++) 282 | { 283 | if ($arraysnlist[$i] -eq ($volserial.substring(0,16))) 284 | { 285 | $arraychoice = $i 286 | } 287 | } 288 | add-content $logfile ('The volume is on the FlashArray ' + $endpoint[$arraychoice].endpoint) 289 | add-content $logfile ('This datastore is a Pure Storage volume named ' + $purevol.name) 290 | add-content $logfile '' 291 | try 292 | { 293 | $volinfo = Get-PfaVolumeSpaceMetrics -Array $EndPoint[$arraychoice] -VolumeName $purevol.name 294 | $usedvolcap = ((1 - $volinfo.thin_provisioning)*$volinfo.size)/1024/1024/1024 295 | $usedspace = $datastore.CapacityGB - $datastore.FreeSpaceGB 296 | $deadspace = '{0:N0}' -f ($usedvolcap - $usedspace) 297 | $deadspace = $deadspace -replace ',','' 298 | $deadspace = [System.Math]::Floor($deadspace) 299 | $deadspace = [convert]::ToInt32($deadspace, 10) 300 | } 301 | catch 302 | { 303 | add-content $logfile $error[0] 304 | return 305 | } 306 | if ($deadspace -ge $unmapthreshold) 307 | { 308 | add-content $logfile ('This volume has ' + $deadspace + ' GB of dead space.') 309 | add-content $logfile ('This is greater than the specified UNMAP threshold of ' + $unmapthreshold + ' GB and should be reclaimed.') 310 | $datastorestounmap += New-Object psobject -Property @{DeadSpaceGB=$($deadspace);DatastoreName=$($datastore.name)} 311 | $totaldeadspace = $totaldeadspace + $deadspace 312 | } 313 | else 314 | { 315 | add-content $logfile ('This volume has ' + $deadspace + ' GB of dead space.') 316 | add-content $logfile ('This is less than the specified UNMAP threshold of ' + $unmapthreshold + ' GB and will be skipped.') 317 | } 318 | add-content $logfile '' 319 | add-content $logfile '----------------------------------------------------------------------------------------------------' 320 | } 321 | } 322 | else 323 | { 324 | add-content $logfile ('The volume is not a FlashArray device, skipping...') 325 | add-content $logfile '' 326 | add-content $logfile '----------------------------------------------------------------------------------------------------' 327 | } 328 | } 329 | elseif ($lun.count -gt 1) 330 | { 331 | add-content $logfile ('The volume spans more than one SCSI device, skipping...') 332 | add-content $logfile '' 333 | add-content $logfile '----------------------------------------------------------------------------------------------------' 334 | } 335 | } 336 | } 337 | add-content $logfile "" 338 | add-content $logfile ("Analysis for all volumes is complete. Minimum total possible virtual space that can be reclaimed is " + $totaldeadspace + " GB:") 339 | if ($datastorestounmap.count -gt 0) 340 | { 341 | write-host ("Analysis for all volumes is complete. Minimum total possible virtual space that can be reclaimed is " + $totaldeadspace + " GB:") 342 | write-host ("The following datastores have more than " + $unmapthreshold + " GB of virtual dead space and are recommended for UNMAP") 343 | write-host ($datastorestounmap |ft -autosize -Property DatastoreName,DeadSpaceGB | Out-String ) 344 | $datastorestounmap|ft -autosize -Property DatastoreName,DeadSpaceGB | Out-File -FilePath $logfile -Append -Encoding ASCII 345 | } 346 | else 347 | { 348 | write-host "No datastores were identified with dead virtual space above the threshold." 349 | } 350 | #disconnecting sessions 351 | add-content $logfile ("Disconnecting vCenter and FlashArray sessions") 352 | disconnect-viserver -Server $vcenter -confirm:$false 353 | foreach ($flasharray in $endpoint) 354 | { 355 | Disconnect-PfaArray -Array $flasharray 356 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | {description} 294 | Copyright (C) {year} {fullname} 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | 341 | -------------------------------------------------------------------------------- /LogInsightIngestionRESTexample.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | *******Disclaimer:****************************************************** 3 | This scripts are offered "as is" with no warranty. While this 4 | scripts is tested and working in my environment, it is recommended that you test 5 | this script in a test lab before using in a production environment. Everyone can 6 | use the scripts/commands provided here without any written permission but I 7 | will not be liable for any damage or loss to the system. 8 | ************************************************************************ 9 | see more info at: 10 | 11 | http://wp.me/p6acjZ-Ci 12 | 13 | #> 14 | 15 | 16 | write-host ' __________________________' 17 | write-host ' /++++++++++++++++++++++++++\' 18 | write-host ' /++++++++++++++++++++++++++++\' 19 | write-host ' /++++++++++++++++++++++++++++++\' 20 | write-host ' /++++++++++++++++++++++++++++++++\' 21 | write-host ' /++++++++++++++++++++++++++++++++++\' 22 | write-host ' /++++++++++++/----------\++++++++++++\' 23 | write-host ' /++++++++++++/ \++++++++++++\' 24 | write-host ' /++++++++++++/ \++++++++++++\' 25 | write-host ' /++++++++++++/ \++++++++++++\' 26 | write-host ' /++++++++++++/ \++++++++++++\' 27 | write-host ' \++++++++++++\ /++++++++++++/' 28 | write-host ' \++++++++++++\ /++++++++++++/' 29 | write-host ' \++++++++++++\ /++++++++++++/' 30 | write-host ' \++++++++++++\ /++++++++++++/' 31 | write-host ' \++++++++++++\ /++++++++++++/' 32 | write-host ' \++++++++++++\' 33 | write-host ' \++++++++++++\' 34 | write-host ' \++++++++++++\' 35 | write-host ' \++++++++++++\' 36 | write-host ' \------------\' 37 | write-host 'Log Insight Ingestion REST API Example' 38 | write-host '----------------------------------------------------------------------------------------------------' 39 | 40 | $loginsightserver = "10.21.10.27" 41 | $loginsightagentID = "624a9370" 42 | 43 | $restvmfs = [ordered]@{ 44 | name = "Datastore" 45 | content = "vmfs" 46 | } 47 | $restarray = [ordered]@{ 48 | name = "FlashArray" 49 | content = "array" 50 | } 51 | $restvol = [ordered]@{ 52 | name = "FlashArrayvol" 53 | content = "vol" 54 | } 55 | $restunmap = [ordered]@{ 56 | name = "ReclaimedSpace" 57 | content = "454" 58 | } 59 | $esxhost = [ordered]@{ 60 | name = "ESXihost" 61 | content = "host" 62 | } 63 | $devicenaa = [ordered]@{ 64 | name = "SCSINaa" 65 | content = "naa.624a9370a847c250adbb1c7b00011aca" 66 | } 67 | $fields = @($restvmfs,$restarray,$restvol,$restunmap,$esxhost,$devicenaa) 68 | $restcall = @{ 69 | messages = ([Object[]]($messages = [ordered]@{ 70 | text = "Completed a VMFS UNMAP on a FlashArray volume." 71 | fields = ([Object[]]$fields) 72 | })) 73 | } |convertto-json -Depth 4 74 | 75 | $resturl = ("http://" + $loginsightserver + ":9000/api/v1/messages/ingest/" + $loginsightagentID) 76 | write-host ("Posting results to Log Insight server: " + $loginsightserver) 77 | try 78 | { 79 | $response = Invoke-RestMethod $resturl -Method Post -Body $restcall -ContentType 'application/json' -ErrorAction stop 80 | write-host "REST Call to Log Insight server successful" 81 | write-host $response 82 | } 83 | catch 84 | { 85 | write-host "REST Call failed to Log Insight server" 86 | write-host $error[0] 87 | write-host $resturl 88 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # powercli 2 | VMware PowerCLI scripts with Pure Storage 3 | -------------------------------------------------------------------------------- /VMwareFlashArraySATPCleanup.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | *******Disclaimer:****************************************************** 3 | This scripts are offered "as is" with no warranty. While this 4 | scripts is tested and working in my environment, it is recommended that you test 5 | this script in a test lab before using in a production environment. Everyone can 6 | use the scripts/commands provided here without any written permission but I 7 | will not be liable for any damage or loss to the system. 8 | ************************************************************************ 9 | 10 | This script will: 11 | -Looks for FlashArray SATP Rules of the Rule Group "system" 12 | -Look for "user rules on those host for the FlashArray. If they are the same (model, vendor, PSP, PSP options) delete the user rule. 13 | 14 | All change operations are logged to a file. 15 | 16 | This can be run directly from PowerCLI or from a standard PowerShell prompt. PowerCLI must be installed on the local host regardless. 17 | 18 | Supports: 19 | -FlashArray 400 Series and //m 20 | -vCenter 5.5 and later 21 | -PowerCLI 6.5 R1 or later required 22 | 23 | For info, refer to www.codyhosterman.com 24 | #> 25 | write-host "Please choose a directory to store the script log" 26 | function ChooseFolder([string]$Message, [string]$InitialDirectory) 27 | { 28 | $app = New-Object -ComObject Shell.Application 29 | $folder = $app.BrowseForFolder(0, $Message, 0, $InitialDirectory) 30 | $selectedDirectory = $folder.Self.Path 31 | return $selectedDirectory 32 | } 33 | $logfolder = ChooseFolder -Message "Please select a log file directory" -InitialDirectory 'MyComputer' 34 | $logfile = $logfolder + '\' + (Get-Date -Format o |ForEach-Object {$_ -Replace ':', '.'}) + "satpcleanup.txt" 35 | write-host "Script result log can be found at $logfile" -ForegroundColor Green 36 | add-content $logfile ' __________________________' 37 | add-content $logfile ' /++++++++++++++++++++++++++\' 38 | add-content $logfile ' /++++++++++++++++++++++++++++\' 39 | add-content $logfile ' /++++++++++++++++++++++++++++++\' 40 | add-content $logfile ' /++++++++++++++++++++++++++++++++\' 41 | add-content $logfile ' /++++++++++++++++++++++++++++++++++\' 42 | add-content $logfile ' /++++++++++++/----------\++++++++++++\' 43 | add-content $logfile ' /++++++++++++/ \++++++++++++\' 44 | add-content $logfile ' /++++++++++++/ \++++++++++++\' 45 | add-content $logfile ' /++++++++++++/ \++++++++++++\' 46 | add-content $logfile ' /++++++++++++/ \++++++++++++\' 47 | add-content $logfile ' \++++++++++++\ /++++++++++++/' 48 | add-content $logfile ' \++++++++++++\ /++++++++++++/' 49 | add-content $logfile ' \++++++++++++\ /++++++++++++/' 50 | add-content $logfile ' \++++++++++++\ /++++++++++++/' 51 | add-content $logfile ' \++++++++++++\ /++++++++++++/' 52 | add-content $logfile ' \++++++++++++\' 53 | add-content $logfile ' \++++++++++++\' 54 | add-content $logfile ' \++++++++++++\' 55 | add-content $logfile ' \++++++++++++\' 56 | add-content $logfile ' \------------\' 57 | add-content $logfile 'Pure Storage FlashArray VMware ESXi SATP Cleanup Script v1.0' 58 | add-content $logfile '----------------------------------------------------------------------------------------------------' 59 | 60 | 61 | #Import PowerCLI. Requires PowerCLI version 6.3 or later. Will fail here if PowerCLI is not installed 62 | #Will try to install PowerCLI with PowerShellGet if PowerCLI is not present. 63 | 64 | if ((!(Get-Module -Name VMware.VimAutomation.Core -ErrorAction SilentlyContinue)) -and (!(get-Module -Name VMware.PowerCLI -ListAvailable))) { 65 | if (Test-Path “C:\Program Files (x86)\VMware\Infrastructure\PowerCLI\Scripts\Initialize-PowerCLIEnvironment.ps1”) 66 | { 67 | . “C:\Program Files (x86)\VMware\Infrastructure\PowerCLI\Scripts\Initialize-PowerCLIEnvironment.ps1” |out-null 68 | } 69 | elseif (Test-Path “C:\Program Files (x86)\VMware\Infrastructure\vSphere PowerCLI\Scripts\Initialize-PowerCLIEnvironment.ps1”) 70 | { 71 | . “C:\Program Files (x86)\VMware\Infrastructure\vSphere PowerCLI\Scripts\Initialize-PowerCLIEnvironment.ps1” |out-null 72 | } 73 | elseif (!(get-Module -Name VMware.PowerCLI -ListAvailable)) 74 | { 75 | if (get-Module -name PowerShellGet -ListAvailable) 76 | { 77 | try 78 | { 79 | Get-PackageProvider -name NuGet -ListAvailable -ErrorAction stop 80 | } 81 | catch 82 | { 83 | Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force -Scope CurrentUser -Confirm:$false 84 | } 85 | Install-Module -Name VMware.PowerCLI –Scope CurrentUser -Confirm:$false -Force 86 | } 87 | else 88 | { 89 | write-host ("PowerCLI could not automatically be installed because PowerShellGet is not present. Please install PowerShellGet or PowerCLI") -BackgroundColor Red 90 | write-host "PowerShellGet can be found here https://www.microsoft.com/en-us/download/details.aspx?id=51451 or is included with PowerShell version 5" 91 | write-host "Terminating Script" -BackgroundColor Red 92 | return 93 | } 94 | } 95 | if ((!(Get-Module -Name VMware.VimAutomation.Core -ErrorAction SilentlyContinue)) -and (!(get-Module -Name VMware.PowerCLI -ListAvailable))) 96 | { 97 | write-host ("PowerCLI not found. Please verify installation and retry.") -BackgroundColor Red 98 | write-host "Terminating Script" -BackgroundColor Red 99 | return 100 | } 101 | } 102 | set-powercliconfiguration -invalidcertificateaction "ignore" -confirm:$false |out-null 103 | Set-PowerCLIConfiguration -Scope User -ParticipateInCEIP $false -confirm:$false|out-null 104 | if ((Get-PowerCLIVersion).build -lt 3737840) 105 | { 106 | write-host "This version of PowerCLI is too old, version 6.3 Release 1 or later is required (Build 3737840)" -BackgroundColor Red 107 | write-host "Found the following build number:" 108 | write-host (Get-PowerCLIVersion).build 109 | write-host "Terminating Script" -BackgroundColor Red 110 | write-host "Get it here: https://my.vmware.com/group/vmware/get-download?downloadGroup=PCLI630R1" 111 | add-content $logfile "This version of PowerCLI is too old, version 6.3 Release 1 or later is required (Build 3737840)" 112 | add-content $logfile "Found the following build number:" 113 | add-content $logfile (Get-PowerCLIVersion).build 114 | add-content $logfile "Terminating Script" 115 | add-content $logfile "Get it here: https://my.vmware.com/web/vmware/details?downloadGroup=PCLI650R1&productId=614" 116 | return 117 | } 118 | $vcenter = read-host "Please enter a vCenter IP or FQDN" 119 | $Creds = $Host.ui.PromptForCredential("vCenter Credentials", "Please enter your vCenter username and password.", "","") 120 | try 121 | { 122 | connect-viserver -Server $vcenter -Credential $Creds -ErrorAction Stop |out-null 123 | add-content $logfile ('Connected to vCenter at ' + $vcenter) 124 | } 125 | catch 126 | { 127 | write-host "Failed to connect to vCenter" -BackgroundColor Red 128 | write-host $vcenter 129 | write-host $Error[0] 130 | write-host "Terminating Script" -BackgroundColor Red 131 | add-content $logfile "Failed to connect to vCenter" 132 | add-content $logfile $vcenter 133 | add-content $logfile $Error[0] 134 | add-content $logfile "Terminating Script" 135 | return 136 | } 137 | $deleteRules = Read-host "Delete unnecessary user rules? (if no, the script will just check for them) (y/n)" 138 | while (($deleteRules -ine "y") -and ($deleteRules -ine "n")) 139 | { 140 | write-host "Invalid entry, please enter y or n" 141 | $deleteRules = Read-host "Delete unnecessary user rules? (if no, the script will just check for them) (y/n)" 142 | } 143 | $hosts = Get-VMHost 144 | foreach ($esx in $hosts) 145 | { 146 | add-content $logfile "-----------------------------------------------------------------------------------------------" 147 | add-content $logfile "-----------------------------------------------------------------------------------------------" 148 | add-content $logfile "Examining ESXi host $($esx.NetworkInfo.hostname)" 149 | $esxcli=get-esxcli -VMHost $esx -v2 150 | $rules = $esxcli.storage.nmp.satp.rule.list.invoke() |where-object {$_.Vendor -eq "PURE"} 151 | $systemFound = $false 152 | foreach ($rule in $rules) 153 | { 154 | if ($rule.RuleGroup -eq "system") 155 | { 156 | add-content $logfile "Found a system rule for the FlashArray. Now looking for any user rules..." 157 | $systemFound = $true 158 | break 159 | } 160 | } 161 | if ($systemFound -eq $true) 162 | { 163 | $userFound = $false 164 | $userDeleted = 0 165 | $foundConflictingRule = $false 166 | $deleteError = $false 167 | foreach ($rule in $rules) 168 | { 169 | if ($rule.RuleGroup -eq "user") 170 | { 171 | add-content $logfile "Found a user rule for the FlashArray. Examining its configuration..." 172 | $userFound = $true 173 | if ($deleteRules -eq "y") 174 | { 175 | if ($rule.PSPOptions -eq "iops=1") 176 | { 177 | add-content $logfile "NOTICE: Found a user rule that matches the system rule setting of an IO Operations Limit of 1. Deleting this rule... " 178 | $satpArgs = $esxcli.storage.nmp.satp.rule.remove.createArgs() 179 | $satpArgs.model = $rule.Model 180 | $satpArgs.vendor = "PURE" 181 | $satpArgs.satp = $rule.Name 182 | $satpArgs.psp = $rule.DefaultPSP 183 | $satpArgs.pspoption = $rule.PSPOptions 184 | add-content $logfile "This rule is incorrect, deleting..." 185 | try 186 | { 187 | $esxcli.storage.nmp.satp.rule.remove.invoke($satpArgs) |out-null 188 | add-content $logfile "DELETED THE RULE." 189 | $userDeleted++ 190 | } 191 | catch 192 | { 193 | add-content $logfile "ERROR!!!: Could not delete this SATP Rule. Refer to the vmkernel log for details." 194 | $deleteError =$true 195 | continue 196 | } 197 | } 198 | else 199 | { 200 | add-content $logfile "NOTICE: Found a FlashArray rule that differs from the system FlashArray rule setting of an IO Operations Limit of 1. Will not delete this rule." 201 | add-content $logfile "This user rule is configured with $($rule.PSPoptions). Review and adjust if needed." 202 | $foundConflictingRule = $true 203 | } 204 | } 205 | else 206 | { 207 | if ($rule.PSPOptions -eq "iops=1") 208 | { 209 | add-content $logfile "NOTICE: Found a FlashArray user rule that matches the system FlashArray rule setting of an IO Operations Limit of 1." 210 | $userDeleted++ 211 | } 212 | else 213 | { 214 | add-content $logfile "NOTICE: Found a rule that differs from the FlashArray system rule setting of an IO Operations Limit of 1." 215 | add-content $logfile "This user rule is configured with $($rule.PSPoptions). Review and adjust if needed." 216 | $foundConflictingRule = $true 217 | } 218 | } 219 | 220 | } 221 | } 222 | if ($userFound -eq $false) 223 | { 224 | add-content $logfile "Found zero user rules for the FlashArray on this host" 225 | } 226 | elseif($userDeleted -gt 0) 227 | { 228 | if ($deleteRules -eq "y") 229 | { 230 | add-content $logfile "Deleted $($userDeleted) user rules for the FlashArray on this host" 231 | } 232 | } 233 | if ($foundConflictingRule -eq $true) 234 | { 235 | add-content $logfile "***WARNING***: This host has a conflicting FlashArray user rule that differs from the recommended system rule configuration. Verify this is expected." 236 | } 237 | elseif ($deleteError -eq $true) 238 | { 239 | add-content $logfile "***ERROR***: This host has an unnecessary FlashArray user rule but it could not be deleted!" 240 | } 241 | elseif (($deleteRules -eq "y") -and ($userDeleted -gt 0)) 242 | { 243 | add-content $logfile "***SUCCESS***: Unnecessary FlashArray user rules have been removed and this host is now in a clean state!" 244 | } 245 | elseif (($deleteRules -eq "n") -and ($userDeleted -gt 0)) 246 | { 247 | add-content $logfile "***WARNING***: This host has unnecessary FlashArray user rules!" 248 | } 249 | elseif ($userDeleted -eq 0) 250 | { 251 | add-content $logfile "***SUCCESS***: This host is in a clean state and no changes are required!" 252 | } 253 | 254 | } 255 | else 256 | { 257 | add-content $logfile "Did not find a FlashArray system rule. Skipping this host." 258 | } 259 | } 260 | add-content $logfile "-----------------------------------------------------------------------------------------------" 261 | disconnect-viserver -Server $vcenter -confirm:$false 262 | add-content $logfile "Disconnected vCenter connection" 263 | write-host "Script complete. Refer to log for details." -------------------------------------------------------------------------------- /bestpractices.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | Optional parameters. Keep these values at default unless necessary and understood 3 | For a different IO Operations limit beside the Pure Storage recommended value of 1, change $iopsvalue to another integer value 1-1000. 4 | To skip changing host-wide settings for XCOPY Transfer Size and In-Guest UNMAP change $hostwidesettings to $false 5 | #> 6 | $iopsvalue = 1 7 | 8 | <# 9 | *******Disclaimer:****************************************************** 10 | This scripts are offered "as is" with no warranty. While this 11 | scripts is tested and working in my environment, it is recommended that you test 12 | this script in a test lab before using in a production environment. Everyone can 13 | use the scripts/commands provided here without any written permission but I 14 | will not be liable for any damage or loss to the system. 15 | ************************************************************************ 16 | 17 | This script will: 18 | -Set Disk.DiskMaxIOSize to 4 MB (if indicated) 19 | -Check for a SATP rule for Pure Storage FlashArrays 20 | -Create a SATP rule for Round Robin and IO Operations Limit of 1 only for FlashArrays 21 | -Remove any incorrectly configured Pure Storage FlashArray rules 22 | -Configure any existing devices properly (Pure Storage FlashArray devices only) 23 | -Check all VMFS-6 volumes that automatic UNMAP is enabled 24 | 25 | All change operations are logged to a file. 26 | 27 | This can be run directly from PowerCLI or from a standard PowerShell prompt. PowerCLI must be installed on the local host regardless. 28 | 29 | Supports: 30 | -FlashArray 400 Series, //m, and //x 31 | -vCenter 5.5 and later 32 | -PowerCLI 6.3 R1 or later required 33 | 34 | For info, refer to www.codyhosterman.com 35 | #> 36 | write-host "Please choose a directory to store the script log" 37 | function ChooseFolder([string]$Message, [string]$InitialDirectory) 38 | { 39 | $app = New-Object -ComObject Shell.Application 40 | $folder = $app.BrowseForFolder(0, $Message, 0, $InitialDirectory) 41 | $selectedDirectory = $folder.Self.Path 42 | return $selectedDirectory 43 | } 44 | $logfolder = ChooseFolder -Message "Please select a log file directory" -InitialDirectory 'MyComputer' 45 | $logfile = $logfolder + '\' + (Get-Date -Format o |ForEach-Object {$_ -Replace ':', '.'}) + "setbestpractices.log" 46 | 47 | write-host "Checking and setting Pure Storage FlashArray Best Practices for VMware on the ESXi hosts in this vCenter." 48 | write-host "" 49 | 50 | add-content $logfile ' __________________________' 51 | add-content $logfile ' /++++++++++++++++++++++++++\' 52 | add-content $logfile ' /++++++++++++++++++++++++++++\' 53 | add-content $logfile ' /++++++++++++++++++++++++++++++\' 54 | add-content $logfile ' /++++++++++++++++++++++++++++++++\' 55 | add-content $logfile ' /++++++++++++++++++++++++++++++++++\' 56 | add-content $logfile ' /++++++++++++/----------\++++++++++++\' 57 | add-content $logfile ' /++++++++++++/ \++++++++++++\' 58 | add-content $logfile ' /++++++++++++/ \++++++++++++\' 59 | add-content $logfile ' /++++++++++++/ \++++++++++++\' 60 | add-content $logfile ' /++++++++++++/ \++++++++++++\' 61 | add-content $logfile ' \++++++++++++\ /++++++++++++/' 62 | add-content $logfile ' \++++++++++++\ /++++++++++++/' 63 | add-content $logfile ' \++++++++++++\ /++++++++++++/' 64 | add-content $logfile ' \++++++++++++\ /++++++++++++/' 65 | add-content $logfile ' \++++++++++++\ /++++++++++++/' 66 | add-content $logfile ' \++++++++++++\' 67 | add-content $logfile ' \++++++++++++\' 68 | add-content $logfile ' \++++++++++++\' 69 | add-content $logfile ' \++++++++++++\' 70 | add-content $logfile ' \------------\' 71 | add-content $logfile 'Pure Storage FlashArray VMware ESXi Best Practices Script v 4.5 (APRIL-2018)' 72 | add-content $logfile '----------------------------------------------------------------------------------------------------' 73 | 74 | #Import PowerCLI. Requires PowerCLI version 6.3 or later. Will fail here if PowerCLI cannot be installed 75 | #Will try to install PowerCLI with PowerShellGet if PowerCLI is not present. 76 | 77 | if ((!(Get-Module -Name VMware.VimAutomation.Core -ErrorAction SilentlyContinue)) -and (!(get-Module -Name VMware.PowerCLI -ListAvailable))) { 78 | if (Test-Path “C:\Program Files (x86)\VMware\Infrastructure\PowerCLI\Scripts\Initialize-PowerCLIEnvironment.ps1”) 79 | { 80 | . “C:\Program Files (x86)\VMware\Infrastructure\PowerCLI\Scripts\Initialize-PowerCLIEnvironment.ps1” |out-null 81 | } 82 | elseif (Test-Path “C:\Program Files (x86)\VMware\Infrastructure\vSphere PowerCLI\Scripts\Initialize-PowerCLIEnvironment.ps1”) 83 | { 84 | . “C:\Program Files (x86)\VMware\Infrastructure\vSphere PowerCLI\Scripts\Initialize-PowerCLIEnvironment.ps1” |out-null 85 | } 86 | elseif (!(get-Module -Name VMware.PowerCLI -ListAvailable)) 87 | { 88 | if (get-Module -name PowerShellGet -ListAvailable) 89 | { 90 | try 91 | { 92 | Get-PackageProvider -name NuGet -ListAvailable -ErrorAction stop 93 | } 94 | catch 95 | { 96 | Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force -Scope CurrentUser -Confirm:$false 97 | } 98 | Install-Module -Name VMware.PowerCLI –Scope CurrentUser -Confirm:$false -Force 99 | } 100 | else 101 | { 102 | write-host ("PowerCLI could not automatically be installed because PowerShellGet is not present. Please install PowerShellGet or PowerCLI") -BackgroundColor Red 103 | write-host "PowerShellGet can be found here https://www.microsoft.com/en-us/download/details.aspx?id=51451 or is included with PowerShell version 5" 104 | write-host "Terminating Script" -BackgroundColor Red 105 | return 106 | } 107 | } 108 | if ((!(Get-Module -Name VMware.VimAutomation.Core -ListAvailable -ErrorAction SilentlyContinue)) -and (!(get-Module -Name VMware.PowerCLI -ListAvailable))) 109 | { 110 | write-host ("PowerCLI not found. Please verify installation and retry.") -BackgroundColor Red 111 | write-host "Terminating Script" -BackgroundColor Red 112 | return 113 | } 114 | } 115 | set-powercliconfiguration -invalidcertificateaction "ignore" -confirm:$false |out-null 116 | if ((Get-PowerCLIVersion).build -lt 3737840) 117 | { 118 | write-host "This version of PowerCLI is too old, version 6.3 Release 1 or later is required (Build 3737840)" -BackgroundColor Red 119 | write-host "Found the following build number:" 120 | write-host (Get-PowerCLIVersion).build 121 | write-host "Terminating Script" -BackgroundColor Red 122 | write-host "Get it here: https://my.vmware.com/group/vmware/get-download?downloadGroup=PCLI630R1" 123 | add-content $logfile "This version of PowerCLI is too old, version 6.3 Release 1 or later is required (Build 3737840)" 124 | add-content $logfile "Found the following build number:" 125 | add-content $logfile (Get-PowerCLIVersion).build 126 | add-content $logfile "Terminating Script" 127 | add-content $logfile "Get it here: https://my.vmware.com/web/vmware/details?downloadGroup=PCLI650R1&productId=614" 128 | return 129 | } 130 | $vcenter = read-host "Please enter a vCenter IP or FQDN" 131 | $Creds = $Host.ui.PromptForCredential("vCenter Credentials", "Please enter your vCenter username and password.", "","") 132 | try 133 | { 134 | connect-viserver -Server $vcenter -Credential $Creds -ErrorAction Stop |out-null 135 | add-content $logfile ('Connected to vCenter at ' + $vcenter) 136 | add-content $logfile '----------------------------------------------------------------------------------------------------' 137 | } 138 | catch 139 | { 140 | write-host "Failed to connect to vCenter" -BackgroundColor Red 141 | write-host $vcenter 142 | write-host $Error[0] 143 | write-host "Terminating Script" -BackgroundColor Red 144 | add-content $logfile "Failed to connect to vCenter" 145 | add-content $logfile $vcenter 146 | add-content $logfile $Error[0] 147 | add-content $logfile "Terminating Script" 148 | return 149 | } 150 | write-host "" 151 | add-content $logfile '----------------------------------------------------------------------------------------------------' 152 | 153 | write-host "The default behavior is to check and fix every host in vCenter." 154 | $clusterChoice = read-host "Would you prefer to limit this to hosts in a specific cluster? (y/n)" 155 | 156 | while (($clusterChoice -ine "y") -and ($clusterChoice -ine "n")) 157 | { 158 | write-host "Invalid entry, please enter y or n" 159 | $clusterChoice = "Would you like to limit this check to a single cluster? (y/n)" 160 | } 161 | if ($clusterChoice -ieq "y") 162 | { 163 | write-host "Please choose the cluster in the dialog box that popped-up." -ForegroundColor Yellow 164 | [void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") 165 | [void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing") 166 | 167 | #create form to choose recovery cluster 168 | $ClusterForm = New-Object System.Windows.Forms.Form 169 | $ClusterForm.width = 300 170 | $ClusterForm.height = 100 171 | $ClusterForm.Text = ”Choose a Cluster” 172 | 173 | $DropDown = new-object System.Windows.Forms.ComboBox 174 | $DropDown.DropDownStyle = [System.Windows.Forms.ComboBoxStyle]::DropDownList 175 | $DropDown.Location = new-object System.Drawing.Size(10,10) 176 | $DropDown.Size = new-object System.Drawing.Size(250,30) 177 | $clusters = get-cluster 178 | if ($clusters.count -lt 1) 179 | { 180 | add-content $logfile "Terminating Script. No VMware cluster(s) found." 181 | write-host "No VMware cluster(s) found. Terminating Script" -BackgroundColor Red 182 | disconnectServers 183 | return 184 | } 185 | ForEach ($cluster in $clusters) { 186 | $DropDown.Items.Add($cluster.Name) |out-null 187 | } 188 | $ClusterForm.Controls.Add($DropDown) 189 | 190 | #okay button 191 | $OkClusterButton = new-object System.Windows.Forms.Button 192 | $OkClusterButton.Location = new-object System.Drawing.Size(60,40) 193 | $OkClusterButton.Size = new-object System.Drawing.Size(70,20) 194 | $OkClusterButton.Text = "OK" 195 | $OkClusterButton.Add_Click({ 196 | $script:clusterName = $DropDown.SelectedItem.ToString() 197 | $ClusterForm.Close() 198 | }) 199 | $ClusterForm.Controls.Add($OkClusterButton) 200 | 201 | #cancel button 202 | $CancelClusterButton = new-object System.Windows.Forms.Button 203 | $CancelClusterButton.Location = new-object System.Drawing.Size(150,40) 204 | $CancelClusterButton.Size = new-object System.Drawing.Size(70,20) 205 | $CancelClusterButton.Text = "Cancel" 206 | $CancelClusterButton.Add_Click({ 207 | $script:endscript = $true 208 | $ClusterForm.Close() 209 | }) 210 | $ClusterForm.Controls.Add($CancelClusterButton) 211 | $DropDown.SelectedIndex = 0 212 | $ClusterForm.Add_Shown({$ClusterForm.Activate()}) 213 | [void] $ClusterForm.ShowDialog() 214 | 215 | add-content $logfile "Selected cluster is $($clusterName)" 216 | add-content $logfile "" 217 | $cluster = get-cluster -Name $clusterName 218 | $hosts= $cluster |get-vmhost 219 | write-host "" 220 | } 221 | else 222 | { 223 | write-host "" 224 | $hosts= get-vmhost 225 | } 226 | $diskMaxIOSize = $false 227 | write-host "If you are using vSphere Replication or intend to use UEFI boot for your VMs, Disk.DiskMaxIOSize must be set to 4096 KB from 32768." 228 | $diskIOChoice = read-host "Would you like to make this host-wide change? (y/n)" 229 | 230 | while (($diskIOChoice -ine "y") -and ($diskIOChoice -ine "n")) 231 | { 232 | write-host "Invalid entry, please enter y or n" 233 | $diskIOChoice = read-host "Would you like to make this host-wide change? (y/n)" 234 | } 235 | if ($diskIOChoice -ieq "y") 236 | { 237 | $diskMaxIOSize = $true 238 | } 239 | Write-Host "" 240 | write-host "Script log information can be found at $logfile" -ForegroundColor green 241 | Write-Host "" 242 | write-host "Executing..." 243 | add-content $logfile "Iterating through all ESXi hosts..." 244 | $hosts | out-string | add-content $logfile 245 | 246 | #Iterating through each host in the vCenter 247 | foreach ($esx in $hosts) 248 | { 249 | $esxcli=get-esxcli -VMHost $esx -v2 250 | add-content $logfile "-----------------------------------------------------------------------------------------------" 251 | add-content $logfile "-----------------------------------------------------------------------------------------------" 252 | add-content $logfile "Working on the following ESXi host: $($esx.NetworkInfo.hostname)" 253 | add-content $logfile "-----------------------------------------------" 254 | if ($diskMaxIOSize -eq $true) 255 | { 256 | add-content $logfile "Checking Disk.DiskMaxIoSize setting." 257 | add-content $logfile "-------------------------------------------------------" 258 | $maxiosize = $esx |get-advancedsetting -Name Disk.DiskMaxIOSize 259 | if ($maxiosize.value -gt 4096) 260 | { 261 | add-content $logfile "The Disk.DiskMaxIOSize setting is set too high--currently set at $($maxiosize.value) KB " 262 | add-content $logfile "If your environment uses UEFI boot for VMs they will not boot unless this is set to 4096 (4 MB) or lower." 263 | add-content $logfile "https://docs.vmware.com/en/VMware-vSphere/6.5/com.vmware.vsphere.vm_admin.doc/GUID-898217D4-689D-4EB5-866C-888353FE241C.html" 264 | add-content $logfile "Setting to 4 MB..." 265 | $maxiosize |Set-AdvancedSetting -Value 4096 -Confirm:$false |out-null 266 | } 267 | else 268 | { 269 | add-content $logfile "Disk.DiskMaxIOSize is set properly." 270 | } 271 | add-content $logfile "" 272 | add-content $logfile "-------------------------------------------------------" 273 | } 274 | #checking and setting FlashArray iSCSI targets for best practices (logintimeout of 30 seconds and delayedack to disabled) 275 | $targets = $esxcli.iscsi.adapter.target.portal.list.invoke() | where-object {$_.Target -like "*purestorage*"} 276 | $iscsihba = $esx |Get-vmhosthba|where-object {$_.Model -eq "iSCSI Software Adapter"} 277 | $sendtgts = $iscsihba | Get-IScsiHbaTarget -type send 278 | add-content $logfile "Checking dynamic iSCSI targets that are FlashArray targets...Only ones that need to be fixed will be reported" 279 | foreach ($target in $targets) 280 | { 281 | foreach ($sendtgt in $sendtgts) 282 | { 283 | if ($target.IP -eq $sendtgt.Address) 284 | { 285 | $iscsiargs = $esxcli.iscsi.adapter.discovery.sendtarget.param.get.CreateArgs() 286 | $iscsiargs.adapter = $iscsihba.Device 287 | $iscsiargs.address = $target.IP 288 | $delayedAck = $esxcli.iscsi.adapter.discovery.sendtarget.param.get.invoke($iscsiargs) |where-object {$_.name -eq "DelayedAck"} 289 | $loginTimeout = $esxcli.iscsi.adapter.discovery.sendtarget.param.get.invoke($iscsiargs) |where-object {$_.name -eq "LoginTimeout"} 290 | if ($delayedAck.Current -eq "true") 291 | { 292 | add-content $logfile "DelayedAck is not disabled on dynamic target $($target.target). Disabling... " 293 | $iscsiargs = $esxcli.iscsi.adapter.discovery.sendtarget.param.set.CreateArgs() 294 | $iscsiargs.adapter = $iscsihba.Device 295 | $iscsiargs.address = $target.IP 296 | $iscsiargs.value = "false" 297 | $iscsiargs.key = "DelayedAck" 298 | $esxcli.iscsi.adapter.discovery.sendtarget.param.set.invoke($iscsiargs) |out-null 299 | } 300 | if ($loginTimeout.Current -ne "30") 301 | { 302 | add-content $logfile "LoginTimeout is not set to 30 seconds on dynamic target $($target.target). It is set to $($loginTimeout.Current). Setting to 30... " 303 | $iscsiargs = $esxcli.iscsi.adapter.discovery.sendtarget.param.set.CreateArgs() 304 | $iscsiargs.adapter = $iscsihba.Device 305 | $iscsiargs.address = $target.IP 306 | $iscsiargs.value = "30" 307 | $iscsiargs.key = "LoginTimeout" 308 | $esxcli.iscsi.adapter.discovery.sendtarget.param.set.invoke($iscsiargs) |out-null 309 | } 310 | } 311 | } 312 | } 313 | add-content $logfile "Checking static iSCSI targets that are FlashArray targets...Only ones that need to be fixed will be reported" 314 | $statictgts = $iscsihba | Get-IScsiHbaTarget -type static 315 | foreach ($target in $targets) 316 | { 317 | foreach ($statictgt in $statictgts) 318 | { 319 | if ($target.IP -eq $statictgt.Address) 320 | { 321 | $iscsiargs = $esxcli.iscsi.adapter.target.portal.param.get.CreateArgs() 322 | $iscsiargs.adapter = $iscsihba.Device 323 | $iscsiargs.address = $target.IP 324 | $iscsiargs.name = $target.target 325 | $delayedAck = $esxcli.iscsi.adapter.target.portal.param.get.invoke($iscsiargs) |where-object {$_.name -eq "DelayedAck"} 326 | $loginTimeout = $esxcli.iscsi.adapter.target.portal.param.get.invoke($iscsiargs) |where-object {$_.name -eq "LoginTimeout"} 327 | if ($delayedAck.Current -eq "true") 328 | { 329 | add-content $logfile "DelayedAck is not disabled on static target $($target.target). Disabling... " 330 | $iscsiargs = $esxcli.iscsi.adapter.target.portal.param.set.CreateArgs() 331 | $iscsiargs.adapter = $iscsihba.Device 332 | $iscsiargs.address = $target.IP 333 | $iscsiargs.name = $target.target 334 | $iscsiargs.value = "false" 335 | $iscsiargs.key = "DelayedAck" 336 | $esxcli.iscsi.adapter.target.portal.param.set.invoke($iscsiargs) |out-null 337 | } 338 | if ($loginTimeout.Current -ne "30") 339 | { 340 | add-content $logfile "LoginTimeout is not set to 30 seconds on static target $($target.target). It is set to $($loginTimeout.Current). Setting to 30... " 341 | $iscsiargs = $esxcli.iscsi.adapter.target.portal.param.set.CreateArgs() 342 | $iscsiargs.adapter = $iscsihba.Device 343 | $iscsiargs.address = $target.IP 344 | $iscsiargs.name = $target.target 345 | $iscsiargs.value = "30" 346 | $iscsiargs.key = "LoginTimeout" 347 | $esxcli.iscsi.adapter.target.portal.param.set.invoke($iscsiargs) |out-null 348 | } 349 | } 350 | } 351 | } 352 | #checking for correct multipathing SATP rules 353 | $rules = $esxcli.storage.nmp.satp.rule.list.invoke() |where-object {$_.Vendor -eq "PURE"} 354 | $correctrule = 0 355 | $iopsoption = "iops=" + $iopsvalue 356 | if ($rules.Count -ge 1) 357 | { 358 | add-content $logfile "Found the following existing Pure Storage SATP rules" 359 | ($rules | out-string).TrimEnd() | add-content $logfile 360 | add-content $logfile "-----------------------------------------------" 361 | foreach ($rule in $rules) 362 | { 363 | add-content $logfile "-----------------------------------------------" 364 | add-content $logfile "Checking the following existing rule:" 365 | ($rule | out-string).TrimEnd() | add-content $logfile 366 | $issuecount = 0 367 | if ($rule.DefaultPSP -eq "VMW_PSP_RR") 368 | { 369 | add-content $logfile "The existing Pure Storage FlashArray rule is configured with the correct Path Selection Policy:" 370 | add-content $logfile $rule.DefaultPSP 371 | } 372 | else 373 | { 374 | add-content $logfile "The existing Pure Storage FlashArray rule is NOT configured with the correct Path Selection Policy:" 375 | add-content $logfile $rule.DefaultPSP 376 | add-content $logfile "The rule should be configured to Round Robin (VMW_PSP_RR)" 377 | $issuecount = 1 378 | } 379 | if ($rule.PSPOptions -eq $iopsoption) 380 | { 381 | add-content $logfile "The existing Pure Storage FlashArray rule is configured with the correct IO Operations Limit:" 382 | add-content $logfile $rule.PSPOptions 383 | } 384 | else 385 | { 386 | add-content $logfile "The existing Pure Storage FlashArray rule is NOT configured with the correct IO Operations Limit:" 387 | add-content $logfile $rule.PSPOptions 388 | add-content $logfile "The rule should be configured to an IO Operations Limit of $iopsvalue" 389 | $issuecount = $issuecount + 1 390 | } 391 | if ($rule.Model -eq "FlashArray") 392 | { 393 | add-content $logfile "The existing Pure Storage FlashArray rule is configured with the correct model:" 394 | add-content $logfile $rule.Model 395 | } 396 | else 397 | { 398 | add-content $logfile "The existing Pure Storage FlashArray rule is NOT configured with the correct model:" 399 | add-content $logfile $rule.Model 400 | add-content $logfile "The rule should be configured with the model of FlashArray" 401 | $issuecount = $issuecount + 1 402 | } 403 | if ($issuecount -ge 1) 404 | { 405 | $satpArgs = $esxcli.storage.nmp.satp.rule.remove.createArgs() 406 | $satpArgs.model = $rule.Model 407 | $satpArgs.vendor = "PURE" 408 | $satpArgs.satp = $rule.Name 409 | $satpArgs.psp = $rule.DefaultPSP 410 | $satpArgs.pspoption = $rule.PSPOptions 411 | add-content $logfile "This rule is incorrect, deleting..." 412 | $esxcli.storage.nmp.satp.rule.remove.invoke($satpArgs) 413 | add-content $logfile "*****NOTE: Deleted the rule.*****" 414 | add-content $logfile "-----------------------------------------------" 415 | } 416 | else 417 | { 418 | add-content $logfile "This rule is correct" 419 | add-content $logfile "-----------------------------------------------" 420 | $correctrule = 1 421 | } 422 | } 423 | } 424 | if ($correctrule -eq 0) 425 | { 426 | add-content $logfile "No correct SATP rule for the Pure Storage FlashArray is found. Creating a new rule to set Round Robin and an IO Operations Limit of $iopsvalue" 427 | $satpArgs = $esxcli.storage.nmp.satp.rule.remove.createArgs() 428 | $satpArgs.description = "Pure Storage FlashArray SATP Rule" 429 | $satpArgs.model = "FlashArray" 430 | $satpArgs.vendor = "PURE" 431 | $satpArgs.satp = "VMW_SATP_ALUA" 432 | $satpArgs.psp = "VMW_PSP_RR" 433 | $satpArgs.pspoption = $iopsoption 434 | $result = $esxcli.storage.nmp.satp.rule.add.invoke($satpArgs) 435 | if ($result -eq $true) 436 | { 437 | add-content $logfile "New rule created:" 438 | $newrule = $esxcli.storage.nmp.satp.rule.list.invoke() |where-object {$_.Vendor -eq "PURE"} 439 | $newrule | out-string | add-content $logfile 440 | } 441 | else 442 | { 443 | add-content $logfile "ERROR: The rule failed to create. Manual intervention might be required." 444 | } 445 | } 446 | else 447 | { 448 | add-content $logfile "A correct SATP rule for the FlashArray exists. No need to create a new one on this host." 449 | } 450 | $devices = $esx |Get-ScsiLun -CanonicalName "naa.624a9370*" 451 | add-content $logfile "-----------------------------------------------" 452 | if ($devices.count -ge 1) 453 | { 454 | add-content $logfile "Looking for existing Pure Storage volumes on this host" 455 | add-content $logfile "Found the following number of existing Pure Storage volumes on this host." 456 | add-content $logfile $devices.count 457 | add-content $logfile "Checking and fixing their multipathing configuration now." 458 | add-content $logfile "-----------------------------------------------" 459 | foreach ($device in $devices) 460 | { 461 | add-content $logfile "Found and examining the following FlashArray device:" 462 | add-content $logfile $device.CanonicalName 463 | if ($device.MultipathPolicy -ne "RoundRobin") 464 | { 465 | add-content $logfile "This device does not have the correct Path Selection Policy, it is set to:" 466 | add-content $logfile $device.MultipathPolicy 467 | add-content $logfile "Changing to Round Robin." 468 | Get-VMhost $esx |Get-ScsiLun $device |Set-ScsiLun -MultipathPolicy RoundRobin |out-null 469 | } 470 | else 471 | { 472 | add-content $logfile "This device's Path Selection Policy is correctly set to Round Robin. No need to change." 473 | } 474 | $deviceargs = $esxcli.storage.nmp.psp.roundrobin.deviceconfig.get.createargs() 475 | $deviceargs.device = $device.CanonicalName 476 | $deviceconfig = $esxcli.storage.nmp.psp.roundrobin.deviceconfig.get.invoke($deviceargs) 477 | $nmpargs = $esxcli.storage.nmp.psp.roundrobin.deviceconfig.set.createargs() 478 | $nmpargs.iops = $iopsvalue 479 | $nmpargs.type = "iops" 480 | if ($deviceconfig.IOOperationLimit -ne $iopsvalue) 481 | { 482 | add-content $logfile "The current IO Operation limit for this device is:" 483 | add-content $logfile $deviceconfig.IOOperationLimit 484 | add-content $logfile "This device's IO Operation Limit is unset or is not set to the value of $iopsvalue. Changing..." 485 | $nmpargs.device = $device.CanonicalName 486 | $esxcli.storage.nmp.psp.roundrobin.deviceconfig.set.invoke($nmpargs) |out-null 487 | } 488 | else 489 | { 490 | add-content $logfile "This device's IO Operation Limit matches the value of $iopsvalue. No need to change." 491 | } 492 | add-content $logfile "-------------------" 493 | } 494 | } 495 | else 496 | { 497 | add-content $logfile "No existing Pure Storage volumes found on this host." 498 | } 499 | if ($esx.Version -like "6.5.*") 500 | { 501 | add-content $logfile "Current ESXi is version 6.5" 502 | add-content $logfile "Checking datastores for VMFS 6 Automatic UNMAP Setting" 503 | $datastores = $esx |get-datastore 504 | foreach ($datastore in $datastores) 505 | { 506 | add-content $logfile "" 507 | $lun = $datastore.ExtensionData.Info.Vmfs.Extent.DiskName |select-object -unique 508 | if ($lun.count -eq 1) 509 | { 510 | add-content $logfile ("The UUID for this volume is " + $datastore.ExtensionData.Info.Vmfs.Extent.DiskName) 511 | $esxcli=get-esxcli -VMHost $esx -v2 512 | if ($lun -like 'naa.624a9370*') 513 | { 514 | if ($datastore.ExtensionData.info.vmfs.version -like "6.*") 515 | { 516 | add-content $logfile ("The VMFS named " + $datastore.name + " is version six. Checking Automatic UNMAP configuration...") 517 | $unmapargs = $esxcli.storage.vmfs.reclaim.config.get.createargs() 518 | $unmapargs.volumelabel = $datastore.name 519 | $unmapresult = $esxcli.storage.vmfs.reclaim.config.get.invoke($unmapargs) 520 | if ($unmapresult.ReclaimPriority -ne "low") 521 | { 522 | add-content $logfile ("Automatic Space Reclamation is not set to low. It is set to " + $unmapresult.ReclaimPriority) 523 | add-content $logfile "Setting to low..." 524 | $unmapsetargs = $esxcli.storage.vmfs.reclaim.config.set.createargs() 525 | $unmapsetargs.volumelabel = $datastore.name 526 | $unmapsetargs.reclaimpriority = "low" 527 | $esxcli.storage.vmfs.reclaim.config.set.invoke($unmapsetargs) 528 | } 529 | elseif ($unmapresult.ReclaimPriority -eq "low") 530 | { 531 | add-content $logfile ("Automatic Space Reclamation is correctly set to low.") 532 | } 533 | } 534 | else 535 | { 536 | add-content $logfile ("The VMFS named " + $datastore.name + " is not version 6 so automatic UNMAP is not supported. Skipping.") 537 | } 538 | } 539 | else 540 | { 541 | add-content $logfile "This is not a FlashArray datastore. Skipping." 542 | } 543 | } 544 | } 545 | } 546 | } 547 | disconnect-viserver -Server $vcenter -confirm:$false 548 | add-content $logfile "Disconnected vCenter connection" -------------------------------------------------------------------------------- /checkandfixUNMAP.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | *******Disclaimer:****************************************************** 3 | This scripts are offered "as is" with no warranty. While this 4 | scripts is tested and working in my environment, it is recommended that you test 5 | this script in a test lab before using in a production environment. Everyone can 6 | use the scripts/commands provided here without any written permission but I 7 | will not be liable for any damage or loss to the system. 8 | ************************************************************************ 9 | 10 | This script will: 11 | -Check and fix host setting EnableBlockDelete if not enabled 12 | -Check and fix host setting EnableVMFS6Unmap if not enabled 13 | -Check and fix datastore setting ReclaimPriority if not enabled 14 | 15 | All change operations are logged to a file. 16 | 17 | This can be run directly from PowerCLI or from a standard PowerShell prompt. PowerCLI must be installed on the local host regardless. 18 | 19 | Supports: 20 | -vCenter 6.0 and later 21 | -PowerCLI 6.3 R1 or later required 22 | 23 | For info, refer to www.codyhosterman.com 24 | #> 25 | write-host "Please choose a directory to store the script log" 26 | function ChooseFolder([string]$Message, [string]$InitialDirectory) 27 | { 28 | $app = New-Object -ComObject Shell.Application 29 | $folder = $app.BrowseForFolder(0, $Message, 0, $InitialDirectory) 30 | $selectedDirectory = $folder.Self.Path 31 | return $selectedDirectory 32 | } 33 | $logfolder = ChooseFolder -Message "Please select a log file directory" -InitialDirectory 'MyComputer' 34 | $logfile = $logfolder + '\' + (Get-Date -Format o |ForEach-Object {$_ -Replace ':', '.'}) + "configureunmap.txt" 35 | write-host "Script result log can be found at $logfile" -ForegroundColor Green 36 | 37 | write-host "Checking and setting Automatic UNMAP Configuration for VMware on the ESXi hosts in this vCenter." 38 | write-host "Script log information can be found at $logfile" 39 | 40 | add-content $logfile ' __________________________' 41 | add-content $logfile ' /++++++++++++++++++++++++++\' 42 | add-content $logfile ' /++++++++++++++++++++++++++++\' 43 | add-content $logfile ' /++++++++++++++++++++++++++++++\' 44 | add-content $logfile ' /++++++++++++++++++++++++++++++++\' 45 | add-content $logfile ' /++++++++++++++++++++++++++++++++++\' 46 | add-content $logfile ' /++++++++++++/----------\++++++++++++\' 47 | add-content $logfile ' /++++++++++++/ \++++++++++++\' 48 | add-content $logfile ' /++++++++++++/ \++++++++++++\' 49 | add-content $logfile ' /++++++++++++/ \++++++++++++\' 50 | add-content $logfile ' /++++++++++++/ \++++++++++++\' 51 | add-content $logfile ' \++++++++++++\ /++++++++++++/' 52 | add-content $logfile ' \++++++++++++\ /++++++++++++/' 53 | add-content $logfile ' \++++++++++++\ /++++++++++++/' 54 | add-content $logfile ' \++++++++++++\ /++++++++++++/' 55 | add-content $logfile ' \++++++++++++\ /++++++++++++/' 56 | add-content $logfile ' \++++++++++++\' 57 | add-content $logfile ' \++++++++++++\' 58 | add-content $logfile ' \++++++++++++\' 59 | add-content $logfile ' \++++++++++++\' 60 | add-content $logfile ' \------------\' 61 | add-content $logfile 'VMware VMFS UNMAP Settings Script v1.0' 62 | add-content $logfile '----------------------------------------------------------------------------------------------------' 63 | 64 | 65 | if ( !(Get-Module -Name VMware.VimAutomation.Core -ErrorAction SilentlyContinue) ) { 66 | if (Test-Path “C:\Program Files (x86)\VMware\Infrastructure\PowerCLI\Scripts\Initialize-PowerCLIEnvironment.ps1”) 67 | { 68 | . “C:\Program Files (x86)\VMware\Infrastructure\PowerCLI\Scripts\Initialize-PowerCLIEnvironment.ps1” |out-null 69 | } 70 | elseif (Test-Path “C:\Program Files (x86)\VMware\Infrastructure\vSphere PowerCLI\Scripts\Initialize-PowerCLIEnvironment.ps1”) 71 | { 72 | . “C:\Program Files (x86)\VMware\Infrastructure\vSphere PowerCLI\Scripts\Initialize-PowerCLIEnvironment.ps1” |out-null 73 | } 74 | if ( !(Get-Module -Name VMware.VimAutomation.Core -ErrorAction SilentlyContinue) ) 75 | { 76 | write-host ("PowerCLI not found. Please verify installation and retry.") -BackgroundColor Red 77 | write-host "Terminating Script" -BackgroundColor Red 78 | add-content $logfile ("PowerCLI not found. Please verify installation and retry.") 79 | add-content $logfile "Terminating Script" 80 | return 81 | } 82 | 83 | } 84 | set-powercliconfiguration -invalidcertificateaction "ignore" -confirm:$false |out-null 85 | if ((Get-PowerCLIVersion).build -lt 3737840) 86 | { 87 | write-host "This version of PowerCLI is too old, version 6.3 Release 1 or later is required (Build 3737840)" -BackgroundColor Red 88 | write-host "Found the following build number:" 89 | write-host (Get-PowerCLIVersion).build 90 | write-host "Terminating Script" -BackgroundColor Red 91 | write-host "Get it here: https://my.vmware.com/group/vmware/get-download?downloadGroup=PCLI630R1" 92 | add-content $logfile "This version of PowerCLI is too old, version 6.3 Release 1 or later is required (Build 3737840)" 93 | add-content $logfile "Found the following build number:" 94 | add-content $logfile (Get-PowerCLIVersion).build 95 | add-content $logfile "Terminating Script" 96 | add-content $logfile "Get it here: https://my.vmware.com/web/vmware/details?downloadGroup=PCLI650R1&productId=614" 97 | return 98 | } 99 | $vcenter = read-host "Please enter a vCenter IP or FQDN" 100 | $Creds = $Host.ui.PromptForCredential("vCenter Credentials", "Please enter your vCenter username and password.", "","") 101 | try 102 | { 103 | connect-viserver -Server $vcenter -Credential $Creds -ErrorAction Stop |out-null 104 | add-content $logfile ('Connected to vCenter at ' + $vcenter) 105 | add-content $logfile '----------------------------------------------------------------------------------------------------' 106 | } 107 | catch 108 | { 109 | write-host "Failed to connect to vCenter" -BackgroundColor Red 110 | write-host $vcenter 111 | write-host $Error[0] 112 | write-host "Terminating Script" -BackgroundColor Red 113 | add-content $logfile "Failed to connect to vCenter" 114 | add-content $logfile $vcenter 115 | add-content $logfile $Error[0] 116 | add-content $logfile "Terminating Script" 117 | return 118 | } 119 | write-host "" 120 | add-content $logfile '----------------------------------------------------------------------------------------------------' 121 | $hosts= get-vmhost 122 | add-content $logfile "Iterating through all ESXi hosts..." 123 | #Iterating through each host in the vCenter 124 | write-host "Checking hosts for host-wide UNMAP settings. Only listing hosts that need changed. Refer to the log for details." 125 | foreach ($esx in $hosts) 126 | { 127 | add-content $logfile "--------------------------------------------------------------------------------------------------------" 128 | add-content $logfile "********************************************************************************************************" 129 | add-content $logfile "--------------------------------------------------------------------------------------------------------" 130 | add-content $logfile ("Examining the ESXi host named " + $esx.Name + " (" + $esx.ExtensionData.Config.Network.DnsConfig.HostName + '/' + $esx.ExtensionData.Config.Network.DnsConfig.Address + ")") 131 | if ($esx.Version -like "6.*") 132 | { 133 | add-content $logfile "" 134 | $enableblockdelete = $esx | Get-AdvancedSetting -Name VMFS3.EnableBlockDelete 135 | if ($enableblockdelete.Value -eq 0) 136 | { 137 | add-content $logfile " EnableBlockDelete is currently disabled. Enabling..." 138 | $enableblockdelete |Set-AdvancedSetting -Value 1 -Confirm:$false |out-null 139 | add-content $logfile " EnableBlockDelete has been set to enabled." 140 | write-host (" FIXED: EnableBlockDelete is now enabled on " + $esx.Name + " (" + $esx.ExtensionData.Config.Network.DnsConfig.HostName + '/' + $esx.ExtensionData.Config.Network.DnsConfig.Address + ")") 141 | } 142 | else 143 | { 144 | add-content $logfile " EnableBlockDelete for this host is correctly enabled and will not be altered." 145 | } 146 | if ($esx.Version -like "6.5.*") 147 | { 148 | add-content $logfile "Current ESXi is version 6.5" 149 | $autounmap = $esx | Get-AdvancedSetting -Name VMFS3.EnableVMFS6Unmap 150 | if ($autounmap.Value -eq 0) 151 | { 152 | add-content $logfile "EnableVMFS6Unmap is currently disabled. Enabling..." 153 | $autounmap |Set-AdvancedSetting -Value 1 -Confirm:$false |out-null 154 | add-content $logfile " EnableVMFS6Unmap has been set to enabled." 155 | write-host (" FIXED: EnableVMFS6Unmap is now enabled on " + $esx.Name + " (" + $esx.ExtensionData.Config.Network.DnsConfig.HostName + '/' + $esx.ExtensionData.Config.Network.DnsConfig.Address + ")") 156 | } 157 | else 158 | { 159 | add-content $logfile " EnableVMFS6Unmap for this host is correctly enabled and will not be altered." 160 | } 161 | } 162 | } 163 | else 164 | { 165 | add-content $logfile " The current host is not version 6.x. Skipping..." 166 | } 167 | } 168 | add-content $logfile "--------------------------------------------------------------------------------------------------------" 169 | add-content $logfile "**********************Checking VMFS-6 datastores for Automatic UNMAP Setting...*************************" 170 | add-content $logfile "--------------------------------------------------------------------------------------------------------" 171 | write-host "" 172 | write-host "Checking datastores for Automatic UNMAP setting. Only listing datastores that need changed. Refer to the log for details." 173 | $datastores = get-datastore 174 | foreach ($datastore in $datastores) 175 | { 176 | if ($datastore.Type -eq 'VMFS') 177 | { 178 | if ($datastore.ExtensionData.info.vmfs.version -like "6.*") 179 | { 180 | $esx = $datastore | get-vmhost | where-object {($_.version -like '6.5.*')}| where-object {($_.ConnectionState -eq 'Connected')} |Select-Object -last 1 181 | $esxcli=get-esxcli -VMHost $esx -v2 182 | add-content $logfile "" 183 | add-content $logfile ("The VMFS named " + $datastore.name + " is VMFS version six. Checking Automatic UNMAP configuration...") 184 | $unmapargs = $esxcli.storage.vmfs.reclaim.config.get.createargs() 185 | $unmapargs.volumelabel = $datastore.name 186 | $unmapresult = $esxcli.storage.vmfs.reclaim.config.get.invoke($unmapargs) 187 | if ($unmapresult.ReclaimPriority -ne "low") 188 | { 189 | add-content $logfile (" Automatic Space Reclamation is not set to low. It is set to " + $unmapresult.ReclaimPriority) 190 | add-content $logfile " Setting to low..." 191 | $unmapsetargs = $esxcli.storage.vmfs.reclaim.config.set.createargs() 192 | $unmapsetargs.volumelabel = $datastore.name 193 | $unmapsetargs.reclaimpriority = "low" 194 | $esxcli.storage.vmfs.reclaim.config.set.invoke($unmapsetargs) |Out-Null 195 | add-content $logfile ("Automatic UNMAP was enabled on " + $datastore.Name + " via ESXi host " + $esx.Name + " (" + $esx.ExtensionData.Config.Network.DnsConfig.HostName + '/' + $esx.ExtensionData.Config.Network.DnsConfig.Address + ")") 196 | write-host (" FIXED: Automatic UNMAP is now enabled on datastore " + $datastore.Name) 197 | } 198 | elseif ($unmapresult.ReclaimPriority -eq "low") 199 | { 200 | add-content $logfile (" Automatic Space Reclamation is correctly set to low.") 201 | } 202 | } 203 | else 204 | { 205 | add-content $logfile ("The VMFS named " + $datastore.name + " is not VMFS version 6. Skipping...") 206 | } 207 | } 208 | else 209 | { 210 | add-content $logfile ("The datastore named " + $datastore.name + " is not VMFS. Skipping...") 211 | } 212 | } 213 | disconnect-viserver -Server $vcenter -confirm:$false 214 | add-content $logfile "Disconnected vCenter connection" -------------------------------------------------------------------------------- /createVVolVMDKsforSRM.ps1: -------------------------------------------------------------------------------- 1 | Write-Host " __________________________" 2 | Write-Host " /++++++++++++++++++++++++++\" 3 | Write-Host " /++++++++++++++++++++++++++++\" 4 | Write-Host " /++++++++++++++++++++++++++++++\" 5 | Write-Host " /++++++++++++++++++++++++++++++++\" 6 | Write-Host " /++++++++++++++++++++++++++++++++++\" 7 | Write-Host " /++++++++++++/----------\++++++++++++\" 8 | Write-Host " /++++++++++++/ \++++++++++++\" 9 | Write-Host " /++++++++++++/ \++++++++++++\" 10 | Write-Host " /++++++++++++/ \++++++++++++\" 11 | Write-Host " /++++++++++++/ \++++++++++++\" 12 | Write-Host " \++++++++++++\ /++++++++++++/" 13 | Write-Host " \++++++++++++\ /++++++++++++/" 14 | Write-Host " \++++++++++++\ /++++++++++++/" 15 | Write-Host " \++++++++++++\ /++++++++++++/" 16 | Write-Host " \++++++++++++\ /++++++++++++/" 17 | Write-Host " \++++++++++++\" 18 | Write-Host " \++++++++++++\" 19 | Write-Host " \++++++++++++\" 20 | Write-Host " \++++++++++++\" 21 | Write-Host " \------------\" 22 | Write-Host 23 | Write-host "Pure Storage SRM VVol Creator 1.0" 24 | write-host "----------------------------------------------" 25 | write-host 26 | <# 27 | 28 | Written by Cody Hosterman www.codyhosterman.com 29 | 30 | *******Disclaimer:****************************************************** 31 | This scripts are offered "as is" with no warranty. While this 32 | scripts is tested and working in my environment, it is recommended that you test 33 | this script in a test lab before using in a production environment. Everyone can 34 | use the scripts/commands provided here without any written permission but I 35 | will not be liable for any damage or loss to the system. 36 | ************************************************************************ 37 | 38 | This script takes in a given VMFS and finds all VMs that have VVol-based virtual disks. It also takes in a target VVol datastore. 39 | The script then creates the requisite number of VVols for each VM with the correct sizes on that target VVol datastore. 40 | 41 | Requires: 42 | -VMware PowerCLI 6.5+ 43 | -Microsoft PowerShell 4+ is highly recommended 44 | -FlashArray Purity 5.0+ 45 | -vCenter 6.5+ 46 | #> 47 | If (-NOT ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole(` 48 | [Security.Principal.WindowsBuiltInRole] "Administrator")) 49 | { 50 | Write-Warning "This script has not been run as an administrator." 51 | Write-Warning "Please re-run this script as an administrator!" 52 | write-host "Terminating Script" -BackgroundColor Red 53 | return 54 | } 55 | if ((!(Get-Module -Name VMware.VimAutomation.Core -ErrorAction SilentlyContinue)) -and (!(get-Module -Name VMware.PowerCLI -ListAvailable))) { 56 | if (Test-Path “C:\Program Files (x86)\VMware\Infrastructure\PowerCLI\Scripts\Initialize-PowerCLIEnvironment.ps1”) 57 | { 58 | . “C:\Program Files (x86)\VMware\Infrastructure\PowerCLI\Scripts\Initialize-PowerCLIEnvironment.ps1” |out-null 59 | } 60 | elseif (Test-Path “C:\Program Files (x86)\VMware\Infrastructure\vSphere PowerCLI\Scripts\Initialize-PowerCLIEnvironment.ps1”) 61 | { 62 | . “C:\Program Files (x86)\VMware\Infrastructure\vSphere PowerCLI\Scripts\Initialize-PowerCLIEnvironment.ps1” |out-null 63 | } 64 | elseif (!(get-Module -Name VMware.PowerCLI -ListAvailable)) 65 | { 66 | if (get-Module -name PowerShellGet -ListAvailable) 67 | { 68 | try 69 | { 70 | Get-PackageProvider -name NuGet -ListAvailable -ErrorAction stop 71 | } 72 | catch 73 | { 74 | Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force -Scope CurrentUser -Confirm:$false 75 | } 76 | Install-Module -Name VMware.PowerCLI –Scope CurrentUser -Confirm:$false -Force 77 | } 78 | else 79 | { 80 | write-host ("PowerCLI could not automatically be installed because PowerShellGet is not present. Please install PowerShellGet or PowerCLI") -BackgroundColor Red 81 | write-host "PowerShellGet can be found here https://www.microsoft.com/en-us/download/details.aspx?id=51451 or is included with PowerShell version 5" 82 | write-host "Terminating Script" -BackgroundColor Red 83 | return 84 | } 85 | } 86 | if ((!(Get-Module -Name VMware.VimAutomation.Core -ErrorAction SilentlyContinue)) -and (!(get-Module -Name VMware.PowerCLI -ListAvailable))) 87 | { 88 | write-host ("PowerCLI not found. Please verify installation and retry.") -BackgroundColor Red 89 | write-host "Terminating Script" -BackgroundColor Red 90 | return 91 | } 92 | } 93 | set-powercliconfiguration -invalidcertificateaction "ignore" -confirm:$false |out-null 94 | Set-PowerCLIConfiguration -Scope User -ParticipateInCEIP $false -confirm:$false|out-null 95 | 96 | function disconnectServers{ 97 | disconnect-viserver -Server * -confirm:$false 98 | Remove-PSDrive -name VVol 99 | } 100 | 101 | #connect to source vCenter 102 | $sourcevcenter = read-host "Please enter a source vCenter IP or FQDN" 103 | $Creds = $Host.ui.PromptForCredential("vCenter Credentials", "Please enter your vCenter username and password.", "$env:userdomain\$env:username","") 104 | try 105 | { 106 | connect-viserver -Server $sourcevcenter -Credential $Creds -ErrorAction Stop |out-null 107 | } 108 | catch 109 | { 110 | write-host "Failed to connect to vCenter. Refer to log for details." -BackgroundColor Red 111 | write-host "Terminating Script" -BackgroundColor Red 112 | return 113 | } 114 | 115 | #connect to target vCenter 116 | $targetvcenter = read-host "Please enter a recovery site vCenter IP or FQDN" 117 | $options = [System.Management.Automation.Host.ChoiceDescription[]] @("&Yes", "&No", "&Quit") 118 | [int]$defaultchoice = 1 119 | $opt = $host.UI.PromptForChoice("vCenter Credentials" , "Use the same credentials as source vCenter?" , $Options,$defaultchoice) 120 | if ($opt -eq 2) 121 | { 122 | return 123 | } 124 | elseif ($opt -eq 1) 125 | { 126 | $Creds = $Host.ui.PromptForCredential("vCenter Credentials", "Please enter your vCenter username and password.", "$env:userdomain\$env:username","") 127 | } 128 | try 129 | { 130 | connect-viserver -Server $targetvcenter -Credential $Creds -ErrorAction Stop |out-null 131 | } 132 | catch 133 | { 134 | write-host "Failed to connect to vCenter. Refer to log for details." -BackgroundColor Red 135 | write-host "Terminating Script" -BackgroundColor Red 136 | return 137 | } 138 | 139 | $datastore = Read-Host "Please enter a replicated VMFS datastore name" 140 | $datacenter = Read-Host "Please enter a recovery datacenter name" 141 | $vvolDatastore = Read-Host "Please enter a recovery VVol datastore name"#> 142 | $dc = Get-Datacenter $datacenter -Server $targetvcenter 143 | try 144 | { 145 | $vvolDatastore = Get-Datastore $vvolDatastore -Server $targetvcenter 146 | $vms = Get-Datastore $datastore -Server $sourcevcenter|get-vm 147 | $vmfsUUID = get-datastore $datastore -Server $sourcevcenter|% {$_.ExtensionData.Info.Vmfs.Extent.DiskName} |% {$_.substring(11,17)} 148 | } 149 | catch 150 | { 151 | write-host $Error[0] 152 | write-host "Exiting..." 153 | disconnectServers 154 | return 155 | } 156 | write-host "****************************" 157 | write-host "Identifying all VMs on the datastore $($datastore) and their virtual disks that are VVols" 158 | write-host "" 159 | 160 | #checking for VMs on the VMFS that have VVols and ensuring they are on the same array as the VMFS. Will fail if it finds any elsewhere. 161 | $vvolVMs = @() 162 | foreach ($vm in $vms) 163 | { 164 | $vmdevices = $vm.ExtensionData.Config.Hardware.Device 165 | $foundVVols = 0 166 | foreach ($vmdevice in $vmdevices) 167 | { 168 | if ($vmdevice.gettype().Name -eq "VirtualDisk") 169 | { 170 | if ($vmdevice.Backing.gettype().Name -eq "VirtualDiskFlatVer2BackingInfo") 171 | { 172 | $tempds = $vmdevice.Backing.Datastore 173 | $tempds = Get-Datastore -id $tempds -Server $sourcevcenter 174 | if ($tempds.type -eq "VVOL") 175 | { 176 | #parses VMFS serial number an compares it to VVol datastore UUID to see if they are the same array 177 | $vvolUUID = $tempds.ExtensionData.Info.vvolDS.StorageArray.uuid |% {$_.replace("-","")} |% {$_.substring(16,17)} 178 | if ($vmfsUUID -ne $vvolUUID) 179 | { 180 | write-host "ERROR: The VM $($vm.name) has VVols on a different array that hosts the source datastore $($datastore)" -BackgroundColor Red 181 | write-host "Found a VVol on FlashArray $($vmdevice.Backing.datastore.ExtensionData.Info.vvolDS.StorageArray.name)." -BackgroundColor Red 182 | write-host "Exiting script..." 183 | disconnectServers 184 | return 185 | } 186 | $foundVVols++ 187 | } 188 | } 189 | } 190 | } 191 | if ($foundVVols -gt 0) 192 | { 193 | write-host "Found $($foundVVols) VVols on VM $($VM.name)" 194 | $vvolVMs += $vm 195 | 196 | } 197 | else {write-host "Found ZERO VVols on VM $($VM.name)"} 198 | } 199 | #actually creating the VMDKs for each respective VVol for each VM on the entered datastore. Non-VVol VMDKs (VMFS, NFS, RDMs, will be skipped. 200 | #if the VVol already exists it will be skipped 201 | 202 | write-host "" 203 | write-host "****************************" 204 | write-host "Creating VVol Virtual Disks on $($vvolDatastore.Name)..." 205 | write-host "" 206 | New-PSDrive -Name VVol -Location $vvolDatastore -PSProvider VimDatastore -Root '\' |out-null 207 | $SCfiles = get-childitem -path VVol: -recurse 208 | $vvolVMDKs = $SCfiles| where-object {$_.Name -like "*.vmdk"} 209 | $vvolFolders = $SCfiles| where-object {$_.ItemType -eq "Folder" -and $_.Name -ne ".sdd.sf"} 210 | $virtualDiskManager = Get-View (Get-View ServiceInstance -Server $targetvcenter).Content.virtualDiskManager -Server $targetvcenter 211 | 212 | foreach ($vm in $vvolVMs) 213 | { 214 | write-host "" 215 | write-host "Configuring remote VVols for VM $($vm.name)..." -ForegroundColor Green 216 | $vmdevices = $vm.ExtensionData.Config.Hardware.Device 217 | $foundVVol = $false 218 | $foundFolder = $false 219 | foreach ($vmdevice in $vmdevices) 220 | { 221 | if ($vmdevice.gettype().Name -eq "VirtualDisk") 222 | { 223 | if ($vmdevice.Backing.gettype().Name -eq "VirtualDiskFlatVer2BackingInfo") 224 | { 225 | $tempds = $vmdevice.Backing.Datastore 226 | $tempds = Get-Datastore -id $tempds -Server $sourcevcenter 227 | if ($tempds.type -eq "VVOL") 228 | { 229 | #look to see if VM folder is there 230 | if (($vvolFolders -ne $null) -and ($foundFolder -eq $false)) 231 | { 232 | $foundFolder = $vvolFolders.name.contains($vm.Name) 233 | } 234 | if ($foundFolder -eq $false) 235 | { 236 | write-host "Creating datastore directory for $($VM.name)..." 237 | New-Item -Path VVol:/ -Name $vm.Name -ItemType directory -Confirm:$false -Force:$true |out-null 238 | $foundFolder = $true 239 | start-sleep 2 240 | } 241 | #create VMDK pointer file name 242 | $fileCount = $vmdevice.Backing.fileName |% {$_.indexof("/")} 243 | $fileName = $vmdevice.Backing.fileName |% {$_.substring($fileCount + 1)} 244 | $FilePath ="[$($vvolDatastore.name)] $($vm.name)/$($fileName)" 245 | #check if VVol already exists on target VVol datastore 246 | if ($vvolVMDKs -ne $null) 247 | { 248 | $foundVVol = $vvolVMDKs.name.contains($fileName) 249 | } 250 | if ($foundVVol) 251 | { 252 | write-host "NOTICE: The VVol $($fileName) has already been created. Skipping..." 253 | continue 254 | } 255 | else 256 | { 257 | #create disk configuration 258 | $vHddSpec = new-Object VMWare.Vim.FileBackedVirtualDiskSpec 259 | $vHddSpec.CapacityKB = $vmdevice.CapacityInKB 260 | $vHddSpec.DiskType = "thin" 261 | $vHddSpec.AdapterType = "lsiLogic" 262 | write-host $FilePath 263 | #create VVol VMDK 264 | $virtualDiskManager.CreateVirtualDisk($FilePath, $dc.ExtensionData.moref, $vHddSpec) |out-null 265 | 266 | } 267 | } 268 | } 269 | } 270 | } 271 | } 272 | disconnectServers -------------------------------------------------------------------------------- /disallowsnapshotlunfailover.ps1: -------------------------------------------------------------------------------- 1 | Write-Host " __________________________" 2 | Write-Host " /++++++++++++++++++++++++++\" 3 | Write-Host " /++++++++++++++++++++++++++++\" 4 | Write-Host " /++++++++++++++++++++++++++++++\" 5 | Write-Host " /++++++++++++++++++++++++++++++++\" 6 | Write-Host " /++++++++++++++++++++++++++++++++++\" 7 | Write-Host " /++++++++++++/----------\++++++++++++\" 8 | Write-Host " /++++++++++++/ \++++++++++++\" 9 | Write-Host " /++++++++++++/ \++++++++++++\" 10 | Write-Host " /++++++++++++/ \++++++++++++\" 11 | Write-Host " /++++++++++++/ \++++++++++++\" 12 | Write-Host " \++++++++++++\ /++++++++++++/" 13 | Write-Host " \++++++++++++\ /++++++++++++/" 14 | Write-Host " \++++++++++++\ /++++++++++++/" 15 | Write-Host " \++++++++++++\ /++++++++++++/" 16 | Write-Host " \++++++++++++\ /++++++++++++/" 17 | Write-Host " \++++++++++++\" 18 | Write-Host " \++++++++++++\" 19 | Write-Host " \++++++++++++\" 20 | Write-Host " \++++++++++++\" 21 | Write-Host " \------------\" 22 | Write-Host 23 | Write-host "Pure Storage VMware VMFS Force Mount Script v1.0" 24 | write-host "----------------------------------------------" 25 | write-host 26 | <# 27 | 28 | Written by Cody Hosterman www.codyhosterman.com 29 | 30 | *******Disclaimer:****************************************************** 31 | This scripts are offered "as is" with no warranty. While this 32 | scripts is tested and working in my environment, it is recommended that you test 33 | this script in a test lab before using in a production environment. Everyone can 34 | use the scripts/commands provided here without any written permission but I 35 | will not be liable for any damage or loss to the system. 36 | ************************************************************************ 37 | 38 | This script will take in the source VMFS name and the target VMFS name and refresh the target with the latest snapshot of the source. 39 | Enter in vCenter credentials and one or more FlashArrays. The FlashArrays must use the same credentials. If different credentials 40 | are needed for each FlashArray the script must be altered slightly. 41 | 42 | Supports: 43 | -PowerShell 3.0 or later 44 | -Pure Storage PowerShell SDK 1.0 or later 45 | -PowerCLI 6.0 Release 1 or later (5.5/5.8 is likely fine, but not tested with this script version) 46 | -REST API 1.4 and later 47 | -Purity 4.1 and later 48 | -FlashArray 400 Series and //m 49 | #> 50 | 51 | while ($varsCorrect -ne "Y") 52 | { 53 | $srcarray = Read-Host "Enter the source FlashArray name (do not include DNS suffix)" 54 | $targetflasharray = Read-Host "Enter the target FlashArray IP or FQDN" 55 | $pureuser = Read-Host "Enter FlashArray user name" 56 | $pureuserpwd = Read-Host "Enter FlashArray password" -AsSecureString 57 | $vcenter = Read-Host "Enter vCenter FQDN or IP" 58 | $vcuser = Read-Host "Enter vCenter user name" 59 | $vcpass = Read-Host "Enter vCenter password" -AsSecureString 60 | $csvfile = Read-Host "Enter CSV file directory path" 61 | write-host "" 62 | $varsCorrect = read-host "Are the above values entered accurately? Y/N" 63 | } 64 | 65 | $FACreds = New-Object System.Management.Automation.PSCredential ($pureuser, $pureuserpwd) 66 | $VCCreds = New-Object System.Management.Automation.PSCredential ($vcuser, $vcpass) 67 | $volumes = import-csv $csvfile -UseCulture 68 | 69 | #A function to rescan all of the input ESXi servers and rescan for VMFS volumes. This starts all host operations in parallel and waits for them all to complete. Is called throughout as needed 70 | function rescanESXiHosts 71 | { 72 | foreach ($esxi in $hosts) 73 | { 74 | $argList = @($vcenter, $VCCreds, $esxi) 75 | $job = Start-Job -ScriptBlock{ 76 | Connect-VIServer -Server $args[0] -Credential $args[1] 77 | Get-VMHost -Name $args[2] | Get-VMHostStorage -RescanAllHba -RescanVMFS 78 | Disconnect-VIServer -Confirm:$false 79 | } -ArgumentList $argList 80 | } 81 | Get-Job | Wait-Job |out-null 82 | } 83 | 84 | #A function to just refresh the VMFS storage information on a set of hosts 85 | function refreshESXiHoststorage 86 | { 87 | foreach ($esxi in $hosts) 88 | { 89 | $argList = @($vcenter, $VCCreds, $esxi) 90 | $job = Start-Job -ScriptBlock{ 91 | Connect-VIServer -Server $args[0] -Credential $args[1] 92 | Get-VMHost -Name $args[2] | Get-VMHostStorage -RescanVMFS 93 | Disconnect-VIServer -Confirm:$false 94 | } -ArgumentList $argList 95 | } 96 | Get-Job | Wait-Job |out-null 97 | } 98 | 99 | #A function to unmount and detach the VMFS from all of the input ESXi servers. This starts all host operations in parallel and waits for them all to complete. Is called throughout as needed 100 | function unmountandDetachVMFS 101 | { 102 | foreach ($esxi in $hosts) 103 | { 104 | $argList = @($vcenter, $VCCreds, $esxi, $tempdatastore) 105 | $job = Start-Job -ScriptBlock{ 106 | Connect-VIServer -Server $args[0] -Credential $args[1] 107 | $esxihost = get-vmhost $args[2] 108 | $datastore = get-datastore $args[3] 109 | $storageSystem = Get-View $esxihost.Extensiondata.ConfigManager.StorageSystem 110 | $StorageSystem.UnmountVmfsVolume($datastore.ExtensionData.Info.vmfs.uuid) |out-null 111 | $storageSystem.DetachScsiLun((Get-ScsiLun -VmHost $esxihost | where {$_.CanonicalName -eq $datastore.ExtensionData.Info.Vmfs.Extent.DiskName}).ExtensionData.Uuid) |out-null 112 | Disconnect-VIServer -Confirm:$false 113 | } -ArgumentList $argList 114 | } 115 | Get-Job | Wait-Job |out-null 116 | } 117 | 118 | #disconnects all API connections 119 | function disconnectsessions 120 | { 121 | write-host "Disconnecting vCenter and FlashArrays" -foregroundcolor "red" 122 | disconnect-viserver -Server $vcenter -confirm:$false 123 | Disconnect-PfaArray -Array $targetEndPoint 124 | } 125 | 126 | #Connect to each FlashArray 127 | write-host "Connecting to FlashArray" -foregroundcolor "green" 128 | $targetEndPoint = New-PfaArray -EndPoint $targetflasharray -Credentials $FACreds -IgnoreCertificateError 129 | 130 | #Connect to vCenter 131 | Set-PowerCLIConfiguration -DisplayDeprecationWarnings:$false -confirm:$false| Out-Null 132 | write-host "Connecting to vCenter" -foregroundcolor "green" 133 | connect-viserver -Server $vcenter -Credential $VCCreds|out-null 134 | write-host "*****************************************************************" 135 | write-host "" 136 | 137 | #kills all of the VM process on the affected datastores in the CSV file. Answers the data loss question if the VMs has been tagged with it 138 | foreach ($volume in $volumes) 139 | { 140 | $vms = get-datastore $volume.Source |get-vm 141 | write-host "Killing VMs..." 142 | foreach ($vm in $vms) 143 | { 144 | $vm |Get-VMQuestion |Set-VMQuestion –Option "button.abort" -confirm:$false |Out-Null 145 | $vm = Get-VM $vm.Name 146 | if ($vm.PowerState -eq "PoweredOn") 147 | { 148 | $vm |Stop-VM -Kill -Confirm:$false 149 | } 150 | } 151 | } 152 | #Gets all of the FlashArray snapshots on the target FlashArray 153 | write-host ("Recovering the VMFS copies from the FlashArray named " + $targetEndPoint.EndPoint) 154 | $snaps = Get-PfaallVolumeSnapshots -array $targetEndPoint 155 | $newvols = @() 156 | $finalvols = @() 157 | $volcount = 0 158 | <#The next statement does the following: 159 | 1)Finds the latest snapshot for the given volume 160 | 2) Finds the WWNs of the ESX hosts in that cluster 161 | 3)Find the matching hosts on the FlashArray and their host groups 162 | 4)Creates a new volume from that snapshot 163 | 5)Creates a second new volumes to move the VMs to eventually 164 | 6)Connects them both to the proper hosts or host groups 165 | 7)Will fail if a host group does not match the ESXi cluster (too many hosts, too little, incorrect WWNs) 166 | #> 167 | foreach ($volume in $volumes) 168 | { 169 | write-host "----------------------------------------------------------" 170 | write-host "Starting recovery for the following volume pair:" 171 | write-host $volume 172 | $fullpurevolname = $srcarray+":"+$volume.Source 173 | $snapshots = @() 174 | foreach ($snap in $snaps) 175 | { 176 | if ($snap.source -eq $fullpurevolname) 177 | { 178 | $snapshots += $snap 179 | } 180 | } 181 | $hosts = get-cluster $volume.Cluster |get-vmhost 182 | $hostwwns = @{} 183 | foreach ($esx in $hosts) 184 | { 185 | 186 | $wwns = $esx | Get-VMHostHBA -Type FibreChannel | Select VMHost,Device,@{N="WWN";E={"{0:X}" -f $_.PortWorldWideName}} | Format-table -Property WWN -HideTableHeaders |out-string 187 | $wwns = (($wwns.Replace("`n","")).Replace("`r","")).Replace(" ","") 188 | $wwns = &{for ($i = 0;$i -lt $wwns.length;$i += 16) 189 | { 190 | $wwns.substring($i,16) 191 | }} 192 | $hostwwns.Add($esx.name, $wwns) 193 | } 194 | write-host 195 | $tgthostlist = Get-PfaHosts -Array $targetEndPoint 196 | $hostlist = @() 197 | foreach ($h in $hostwwns.GetEnumerator()) 198 | { 199 | write-host "Looking for host match on target FlashArray for ESXi host" $h.name"..." 200 | $foundhost = 0 201 | foreach ($fahost in $tgthostlist) 202 | { 203 | if ((compare-object $h.value $fahost.wwn) -eq $null) 204 | { 205 | $hostlist += $fahost.name 206 | write-host "Found match for ESXi host name" $h.name "on the target FlashArray" $targetEndPoint.endpoint "named" $fahost.name 207 | $foundhost = 1 208 | } 209 | } 210 | if ($foundhost -ne 1) 211 | { 212 | write-host "Could not find a matching host for ESXi host named" $h.name -foregroundcolor "red" 213 | disconnectsessions 214 | exit 215 | } 216 | } 217 | write-host 218 | write-host "Getting host groups for all of the discovered hosts" 219 | $hostgrouplist = @() 220 | $standalonehosts = @() 221 | foreach ($fahostname in $hostlist) 222 | { 223 | $fahost = Get-PfaHost -Array $targetEndPoint -Name $fahostname 224 | if ($fahost.hgroup -eq $null) 225 | { 226 | write-host "The host" $fahost.name "is not in a host group" 227 | $standalonehosts += $fahost.name 228 | } 229 | else 230 | { 231 | $hostgrouplist += $fahost.hgroup 232 | } 233 | } 234 | try 235 | { 236 | $newvols += New-PfaVolume -Array $targetEndPoint -VolumeName $volume.Target -Source $snapshots[0].name -ErrorAction Stop 237 | } 238 | catch 239 | { 240 | write-host "The recovery volume failed to be created, script exiting" -foregroundcolor "red" 241 | write-host $_.Exception.Message 242 | disconnectsessions 243 | exit 244 | } 245 | write-host 246 | write-host "Created a new volume with the name" $volume.Target "from the snapshot for force mounting" $snapshots[0].name -foregroundcolor "green" 247 | try 248 | { 249 | $finalvols += New-PfaVolume -Array $targetEndPoint -VolumeName $volume.Source -Size $newvols[$volcount].size -ErrorAction Stop 250 | } 251 | catch 252 | { 253 | write-host "The final persistent volume failed to be created, script exiting" -foregroundcolor "red" 254 | write-host $_.Exception.Message 255 | Remove-PfaVolumeOrSnapshot -Array $targetEndPoint -Name $volume.Target -Confirm:$false |out-null 256 | Remove-PfaVolumeOrSnapshot -Array $targetEndPoint -Name $volume.Target -Confirm:$false -Eradicate |out-null 257 | disconnectsessions 258 | exit 259 | } 260 | write-host "Created a new volume with the name" $volume.Source "for persistent VM storage." -foregroundcolor "green" 261 | write-host 262 | if ($hostgrouplist -ge 1) 263 | { 264 | $hostcount = 0 265 | $hostgrouplist = $hostgrouplist |select-object -unique 266 | foreach ($hgroup in $hostgrouplist) 267 | { 268 | $hgroupinfo = Get-PfaHostGroup -Array $targetEndPoint -Name $hgroup 269 | $hostcount = $hostcount + $hgroupinfo.hosts.count 270 | } 271 | if (($hostcount + $standalonehosts.count) -ne $hosts.count) 272 | { 273 | write-host "There are a different number of hosts in the host groups ("$hostcount ") than the number of identified ESXi hosts (" ($hosts.count - $standalonehosts.count) ")" -foregroundcolor "red" 274 | Remove-PfaVolumeOrSnapshot -Array $targetEndPoint -Name $volume.Target -Confirm:$false |out-null 275 | Remove-PfaVolumeOrSnapshot -Array $targetEndPoint -Name $volume.Target -Confirm:$false -Eradicate |out-null 276 | Remove-PfaVolumeOrSnapshot -Array $targetEndPoint -Name $volume.Source -Confirm:$false |out-null 277 | Remove-PfaVolumeOrSnapshot -Array $targetEndPoint -Name $volume.Source -Confirm:$false -Eradicate |out-null 278 | disconnectsessions 279 | exit 280 | } 281 | write-host 282 | write-host $hostgrouplist.count "host group(s) has been identified. Adding the volume to the following host group(s)" 283 | write-host $hostgrouplist 284 | foreach ($hgroup in $hostgrouplist) 285 | { 286 | New-PfaHostGroupVolumeConnection -Array $targetEndPoint -VolumeName $volume.Target -HostGroupName $hgroup |out-null 287 | New-PfaHostGroupVolumeConnection -Array $targetEndPoint -VolumeName $volume.Source -HostGroupName $hgroup |out-null 288 | } 289 | write-host "The new volume" $newvols[$volcount].name "has been added to the following host groups" 290 | write-host $hostgrouplist 291 | } 292 | if ($standalonehosts -gt 0) 293 | { 294 | write-host $standalonehosts.count "standalone hosts have been identified. Adding the volume to the following hosts" 295 | write-host $standalonehosts 296 | foreach ($standalonehost in $standalonehosts) 297 | { 298 | New-PfaHostVolumeConnection -Array $targetEndPoint -VolumeName $volume.Target -HostName $standalonehost |out-null 299 | New-PfaHostVolumeConnection -Array $targetEndPoint -VolumeName $volume.Source -HostName $standalonehost |out-null 300 | } 301 | } 302 | $volcount++ 303 | } 304 | write-host "----------------------------------------------------------" 305 | #Sets all of the hosts to allow snapshots to be force mounted automatically upon a rescan 306 | $hosts = get-cluster $volumes.Cluster|get-vmhost 307 | foreach ($esx in $hosts) 308 | { 309 | $disallowsnapshot = $esx | Get-AdvancedSetting -Name LVM.DisallowSnapshotLUN 310 | if ($disallowsnapshot.value -eq 1) 311 | { 312 | $snapboolean = "enabled" 313 | } 314 | else 315 | { 316 | $snapboolean = "disabled" 317 | } 318 | write-host 319 | write-host "LVM.DisallowSnapshotLun is currently set to" $snapboolean "on ESXi host" $esx.Name 320 | if ($disallowsnapshot.value -ne 0) 321 | { 322 | write-host "Disabling LVM.DisallowSnapshotLun..." 323 | $disallowsnapshot |Set-AdvancedSetting -Value 0 -Confirm:$false |out-null 324 | write-host "LVM.DisallowSnapshotLun is now disabled." 325 | } 326 | else 327 | { 328 | write-host "Setting is correct. No need to change." 329 | } 330 | } 331 | write-host "----------------------------------------------------------" 332 | write-host 333 | write-host "Rescanning host HBAs..." 334 | rescanESXiHosts 335 | start-sleep -s 10 336 | #Powers on all of the VMs 337 | write-host "Powering on the VMs..." 338 | foreach($volume in $volumes) 339 | { 340 | get-datastore $volume.source |get-vm |Start-VM -RunAsync |Out-Null 341 | } 342 | $hosts = get-cluster $volumes.Cluster|get-vmhost 343 | start-sleep -s 10 344 | $skippeddatastores = @() 345 | #Formats the second volume for each pair as VMFS and renames the original one. Moves all of the VMs to the respective new volume 346 | #Old volumes will be unmounted, detached and destroyed on the FlashArray 347 | for($vmfscount=0;$vmfscount -lt $newvols.count; $vmfscount++) 348 | { 349 | 350 | write-host "----------------------------------------------------------" 351 | $hosts = get-datastore $finalvols[$vmfscount].name | get-vmhost 352 | $datastore = Get-Datastore -Name $finalvols[$vmfscount].name 353 | write-host "Renaming datastore" $finalvols[$vmfscount].name "to" $datastore.Name 354 | $tempdatastore = $datastore | Set-Datastore -Name $newvols[$vmfscount].name 355 | $newvolserial = ("naa.624a9370" + $finalvols[$vmfscount].serial) 356 | write-host "Formatting new target datastore with VMFS..." 357 | $targetdatastore = $hosts[0] |New-Datastore -Name $finalvols[$vmfscount].name -Path $newvolserial.ToLower() -Vmfs 358 | $vms = $tempdatastore |get-vm 359 | refreshESXiHoststorage 360 | write-host "Initiating Storage vMotion evacuation of datastore" $tempdatastore.Name "to final datastore." 361 | foreach ($vm in $vms) 362 | { 363 | $vm | Move-VM -Datastore $targetdatastore -RunAsync |Out-Null 364 | 365 | } 366 | $svmotioncount=0 367 | while ((Get-Datastore -Name $newvols[$vmfscount].name |get-vm) -ne $null) 368 | { 369 | if ($svmotioncount -eq 0) 370 | { 371 | write-host "Waiting for Storage vMotion evacuation of VMFS" $tempdatastore.Name "to target VMFS" $targetdatastore.Name "to complete..." 372 | $svmotioncount=1 373 | } 374 | } 375 | write-host "Virtual machine evacuation complete for VMFS" $tempdatastore.Name -foregroundcolor "green" 376 | Start-Sleep -s 10 377 | $vms = Get-Datastore -Name $newvols[$vmfscount].name 378 | if ($vms -ne $null) 379 | { 380 | write-host "Unmounting, detaching, disconnecting and deleting original force-mounted volume..." 381 | $hosts = $datastore | Get-VMHost 382 | unmountandDetachVMFS 383 | $fahosts = Get-PfaVolumeHostConnections -Array $targetEndPoint -VolumeName $volumes[$vmfscount].Target 384 | $fahosts = $fahosts.host 385 | $fahostgroups = Get-PfaVolumeHostGroupConnections -Array $targetEndPoint -VolumeName $volumes[$vmfscount].Target 386 | $fahostgroups = $fahostgroups.hgroup |get-unique 387 | if ($fahosts.count -ge 1) 388 | { 389 | write-host "The volume" $volumes[$vmfscount].Target " is presented privately to the following hosts:" 390 | write-host $fahosts 391 | write-host "Removing the volume from the host(s)..." -foregroundcolor "red" 392 | foreach($fahost in $fahosts) 393 | { 394 | Remove-PfaHostVolumeConnection -Array $targetEndPoint -VolumeName $volumes[$vmfscount].Target -HostName $fahost |out-null 395 | } 396 | } 397 | if ($fahostgroups.count -ge 1) 398 | { 399 | write-host "The volume" $volumes[$vmfscount].Target " is presented to the following host groups:" 400 | write-host $fahostgroups 401 | write-host "Removing the volume from the host groups(s)..." -foregroundcolor "red" 402 | foreach($fahostgroup in $fahostgroups) 403 | { 404 | Remove-PfaHostGroupVolumeConnection -Array $targetEndPoint -VolumeName $volumes[$vmfscount].Target -HostGroupName $fahostgroup |out-null 405 | } 406 | } 407 | write-host "Destroying the volume named" $volumes[$vmfscount].Target -foregroundcolor "red" 408 | Remove-PfaVolumeOrSnapshot -Array $targetEndPoint -Name $volumes[$vmfscount].Target -Confirm:$false |out-null 409 | write-host "The volume can be recovered from the FlashArray Destroyed Volumes folder for 24 hours." 410 | } 411 | else 412 | { 413 | write-host "The datastore" $tempdatastore.Name "has VMs on it still, skipping removal. Please manually migrate remaining VMs and then remove the volume" -foregroundcolor "red" 414 | $skippeddatastores += $tempdatastore 415 | } 416 | write-host "----------------------------------------------------------" 417 | } 418 | $hosts = get-cluster $volumes.Cluster|get-vmhost 419 | rescanESXiHosts 420 | #Turning off the force mount behavior 421 | foreach ($esx in $hosts) 422 | { 423 | $disallowsnapshot = $esx | Get-AdvancedSetting -Name LVM.DisallowSnapshotLUN 424 | if ($disallowsnapshot.value -eq 1) 425 | { 426 | $snapboolean = "enabled" 427 | } 428 | else 429 | { 430 | $snapboolean = "disabled" 431 | } 432 | write-host 433 | write-host "LVM.DisallowSnapshotLun is currently set to" $snapboolean "on ESXi host" $esx.Name 434 | if ($disallowsnapshot.value -ne 1) 435 | { 436 | write-host "Enabling LVM.DisallowSnapshotLun..." 437 | $disallowsnapshot |Set-AdvancedSetting -Value 1 -Confirm:$false |out-null 438 | write-host "LVM.DisallowSnapshotLun is now enabled." 439 | } 440 | else 441 | { 442 | write-host "Setting is correct. No need to change." 443 | } 444 | } 445 | #If any datastores could not be evacuated or another VM was moved to it, the volume was then not deleted. It will be listed here 446 | if ($skippeddatastores -ne $null) 447 | { 448 | write-host "The following datastores were skipped because they still have VMs that failed to migrate. Please manually migrate the VMs and delete the datastore:" 449 | write-host $skippeddatastores -foregroundcolor "red" 450 | } 451 | write-host "----------------------------------------------------------" 452 | write-host 453 | #disconnecting sessions 454 | disconnectsessions -------------------------------------------------------------------------------- /findFlashArrayVolumeNamefromVMFSName.ps1: -------------------------------------------------------------------------------- 1 |  2 | $flasharrays = @() 3 | $arraycount = Read-Host "How many FlashArrays do you want to search? [Enter a whole number 1 or higher]" 4 | Write-Host "Please enter each FlashArray FQDN or IP one at a time and press enter after each entry" 5 | for ($faentry=1; $faentry -le $arraycount; $faentry++) 6 | { 7 | $flasharrays += Read-Host "Enter FlashArray FQDN or IP" 8 | } 9 | $pureuser = Read-Host "Enter FlashArray user name" 10 | $pureuserpwd = Read-Host "Enter FlashArray password" -AsSecureString 11 | $vcenter = Read-Host "Enter vCenter FQDN or IP" 12 | $vcuser = Read-Host "Enter vCenter user name" 13 | $vcpass = Read-Host "Enter vCenter password" -AsSecureString 14 | $vmfsname = Read-Host "Enter VMFS Name" 15 | $purevolumes=@() 16 | $EndPoint= @() 17 | $FACreds = New-Object System.Management.Automation.PSCredential ($pureuser, $pureuserpwd) 18 | $VCCreds = New-Object System.Management.Automation.PSCredential ($vcuser, $vcpass) 19 | $facount = 0 20 | foreach ($flasharray in $flasharrays) 21 | { 22 | if ($facount -eq 0) 23 | { 24 | $EndPoint = @(New-PfaArray -EndPoint $flasharray -Credentials $FACreds -IgnoreCertificateError) 25 | $purevolumes += Get-PfaVolumes -Array $EndPoint[$facount] 26 | $tempvols = @(Get-PfaVolumes -Array $EndPoint[$facount]) 27 | $arraysnlist = @($tempvols.serial[0].substring(0,16)) 28 | } 29 | else 30 | { 31 | $EndPoint += New-PfaArray -EndPoint $flasharray -Credentials $FACreds -IgnoreCertificateError 32 | $purevolumes += Get-PfaVolumes -Array $EndPoint[$facount] 33 | $tempvols = Get-PfaVolumes -Array $EndPoint[$facount] 34 | $arraysnlist += $tempvols.serial[0].substring(0,16) 35 | } 36 | $facount = $facount + 1 37 | } 38 | connect-viserver -Server $vcenter -Credential $VCCreds|out-null 39 | $datastore = get-datastore $vmfsname 40 | $lun = $datastore.ExtensionData.Info.Vmfs.Extent.DiskName 41 | if ($lun -like 'naa.624a9370*') 42 | { 43 | $volserial = ($lun.ToUpper()).substring(12) 44 | $purevol = $purevolumes | where-object { $_.serial -eq $volserial } 45 | for($i=0; $i -lt $arraysnlist.count; $i++) 46 | { 47 | if ($arraysnlist[$i] -eq ($volserial.substring(0,16))) 48 | { 49 | $arraychoice = $i 50 | } 51 | } 52 | write-host ("The VMFS named " + $vmfsname + " is on a FlashArray named " + $EndPoint[$arraychoice].EndPoint) 53 | write-host ("The FlashArray volume named " + $purevol.name) 54 | } 55 | else 56 | { 57 | write-host 'This datastore is NOT a Pure Storage Volume.' 58 | } 59 | #disconnecting sessions 60 | disconnect-viserver -Server $vcenter -confirm:$false 61 | foreach ($flasharray in $endpoint) 62 | { 63 | Disconnect-PfaArray -Array $flasharray 64 | } 65 | 66 | 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /findforcemountedVMFS.ps1: -------------------------------------------------------------------------------- 1 | Write-Host ' __________________________' 2 | Write-Host ' /++++++++++++++++++++++++++\' 3 | Write-Host ' /++++++++++++++++++++++++++++\' 4 | Write-Host ' /++++++++++++++++++++++++++++++\' 5 | Write-Host ' /++++++++++++++++++++++++++++++++\' 6 | Write-Host ' /++++++++++++++++++++++++++++++++++\' 7 | Write-Host ' /++++++++++++/----------\++++++++++++\' 8 | Write-Host ' /++++++++++++/ \++++++++++++\' 9 | Write-Host ' /++++++++++++/ \++++++++++++\' 10 | Write-Host ' /++++++++++++/ \++++++++++++\' 11 | Write-Host ' /++++++++++++/ \++++++++++++\' 12 | Write-Host ' \++++++++++++\ /++++++++++++/' 13 | Write-Host ' \++++++++++++\ /++++++++++++/' 14 | Write-Host ' \++++++++++++\ /++++++++++++/' 15 | Write-Host ' \++++++++++++\ /++++++++++++/' 16 | Write-Host ' \++++++++++++\ /++++++++++++/' 17 | Write-Host ' \++++++++++++\' 18 | Write-Host ' \++++++++++++\' 19 | Write-Host ' \++++++++++++\' 20 | Write-Host ' \++++++++++++\' 21 | Write-Host ' \------------\' 22 | Write-Host 23 | Write-Host 24 | Write-host 'Find ESXi Force Mounted VMFS Volumes Script v1.0' 25 | write-host '----------------------------------------------' 26 | write-host 27 | #For info, refer to www.codyhosterman.com 28 | 29 | #Enter the following parameters. Put all entries inside the quotes. 30 | #********************************** 31 | $vcenter = '' 32 | $vcuser = '' 33 | $vcpass = '' 34 | $logfolder = 'C:\folder\folder\etc\' 35 | $forcemountlogfile = 'forcemount.txt' 36 | $powercliversion = 6 #only change if your PowerCLI version is earlier than 6.0 37 | #End of parameters 38 | 39 | <# 40 | *******Disclaimer:****************************************************** 41 | This scripts are offered "as is" with no warranty. While this 42 | scripts is tested and working in my environment, it is recommended that you test 43 | this script in a test lab before using in a production environment. Everyone can 44 | use the scripts/commands provided here without any written permission but I 45 | will not be liable for any damage or loss to the system. 46 | ************************************************************************ 47 | 48 | This script will identify any Force Mounted Volumes in your vCenter environment 49 | 50 | This can be run directly from PowerCLI or from a standard PowerShell prompt. PowerCLI must be installed on the local host regardless. 51 | 52 | Supports: 53 | -PowerShell 3.0 or later 54 | -PowerCLI 6.0 Release 1 or later recommended (5.5/5.8 is likely fine, but not tested with this script version) 55 | -vCenter 5.5 and later 56 | -Each FlashArray datastore must be present to at least one ESXi version 5.5 or later host or it will not be reclaimed 57 | #> 58 | 59 | #Create log folder if non-existent 60 | If (!(Test-Path -Path $logfolder)) { New-Item -ItemType Directory -Path $logfolder } 61 | $logfile = $logfolder + (Get-Date -Format o |ForEach-Object {$_ -Replace ':', '.'}) + $forcemountlogfile 62 | 63 | add-content $logfile 'Looking for Force Mounted Volumes' 64 | #Important PowerCLI if not done and connect to vCenter. Adds PowerCLI Snapin if 5.8 and earlier. For PowerCLI no import is needed since it is a module 65 | $snapin = Get-PSSnapin -Name vmware.vimautomation.core -ErrorAction SilentlyContinue 66 | if ($snapin.Name -eq $null ) 67 | { 68 | if ($powercliversion -ne 6) {Add-PsSnapin VMware.VimAutomation.Core} 69 | } 70 | Set-PowerCLIConfiguration -invalidcertificateaction 'ignore' -confirm:$false |out-null 71 | Set-PowerCLIConfiguration -Scope Session -WebOperationTimeoutSeconds -1 -confirm:$false |out-null 72 | connect-viserver -Server $vcenter -username $vcuser -password $vcpass|out-null 73 | add-content $logfile 'Connected to vCenter:' 74 | add-content $logfile $vcenter 75 | $datastores = Get-Datastore 76 | foreach ($datastore in $datastores) 77 | { 78 | if ($datastore.ExtensionData.Info.Vmfs.ForceMountedInfo.Mounted -eq "True") 79 | { 80 | add-content $logfile '----------------------------------------------------------------' 81 | add-content $logfile 'The following datastore is force-mounted:' 82 | add-content $logfile $datastore.Name 83 | add-content $logfile 'The datastore is force-mounted on the following ESXi hosts' 84 | $hosts = $datastore |get-vmhost 85 | add-content $logfile $hosts 86 | } 87 | } 88 | #disconnecting sessions 89 | disconnect-viserver -Server $vcenter -confirm:$false -------------------------------------------------------------------------------- /findsrmVMs.ps1: -------------------------------------------------------------------------------- 1 | #Enter the following parameters. Put all entries inside the quotes: 2 | #********************************** 3 | $vcenter = "vCenter Address" 4 | $vcuser = "username" 5 | $vcpass = "password" 6 | $logfolder = "C:\Users\user\Documents\Results\" 7 | ############################# 8 | 9 | set-powercliconfiguration -invalidcertificateaction "ignore" -confirm:$false |out-null 10 | If (!(Test-Path -Path $logfolder)) { New-Item -ItemType Directory -Path $logfolder } 11 | $logfile = $logfolder + (Get-Date -Format o |ForEach-Object {$_ -Replace ":", "."}) + "SRMProtectedVMs.txt" 12 | 13 | add-content $logfile " __________________________" 14 | add-content $logfile " /++++++++++++++++++++++++++\" 15 | add-content $logfile " /++++++++++++++++++++++++++++\" 16 | add-content $logfile " /++++++++++++++++++++++++++++++\" 17 | add-content $logfile " /++++++++++++++++++++++++++++++++\" 18 | add-content $logfile " /++++++++++++++++++++++++++++++++++\" 19 | add-content $logfile " /++++++++++++/----------\++++++++++++\" 20 | add-content $logfile " /++++++++++++/ \++++++++++++\" 21 | add-content $logfile " /++++++++++++/ \++++++++++++\" 22 | add-content $logfile " /++++++++++++/ \++++++++++++\" 23 | add-content $logfile " /++++++++++++/ \++++++++++++\" 24 | add-content $logfile " \++++++++++++\ /++++++++++++/" 25 | add-content $logfile " \++++++++++++\ /++++++++++++/" 26 | add-content $logfile " \++++++++++++\ /++++++++++++/" 27 | add-content $logfile " \++++++++++++\ /++++++++++++/" 28 | add-content $logfile " \++++++++++++\ /++++++++++++/" 29 | add-content $logfile " \++++++++++++\" 30 | add-content $logfile " \++++++++++++\" 31 | add-content $logfile " \++++++++++++\" 32 | add-content $logfile " \++++++++++++\" 33 | add-content $logfile " \------------\" 34 | add-content $logfile "" 35 | add-content $logfile "Pure Storage SRM Protected VM Inquiry Script" 36 | add-content $logfile "----------------------------------------------" 37 | add-content $logfile "" 38 | 39 | connect-viserver -Server $vcenter -username $vcuser -password $vcpass|out-null 40 | Connect-SrmServer |out-null 41 | $srmapi = $defaultsrmservers.ExtensionData 42 | $srmpgs = $srmapi.protection.listprotectiongroups() 43 | for ($i=0; $i -lt $srmpgs.Count; $i++) 44 | { 45 | $vms = $srmpgs[$i].ListProtectedVMs() 46 | $pgvms = [array] "Virtual Machine list:" 47 | for ($a=0; $a -lt $vms.Count; $a++) 48 | { 49 | 50 | $vm = get-vm -ID $vms[$a].VM.MoRef 51 | $pgvms += $vm.Name 52 | } 53 | $pgname = $srmapi.protection.listprotectiongroups()[$i].GetInfo().Name 54 | add-content $logfile "===================================================================================" 55 | add-content $logfile "******************************Next Protection Group********************************" 56 | add-content $logfile "===================================================================================" 57 | $text = "The following " + $vms.Count + " virtual machines are in the Protection Group named " + $pgname 58 | add-content $logfile $text 59 | add-content $logfile $pgvms 60 | } 61 | disconnect-viserver -Server $vcenter -confirm:$false -------------------------------------------------------------------------------- /getScIDfromArrayID.ps1: -------------------------------------------------------------------------------- 1 | $arrayID = Read-Host "Enter a FlashArray ID" 2 | $md5 = new-object -TypeName System.Security.Cryptography.MD5CryptoServiceProvider 3 | $utf8 = new-object -TypeName System.Text.UTF8Encoding 4 | $hash = $md5.ComputeHash($utf8.GetBytes($arrayID)) 5 | $hash2 = $md5.ComputeHash(($hash)) 6 | $hash2[6] = $hash2[6] -band 0x0f 7 | $hash2[6] = $hash2[6] -bor 0x30 8 | $hash2[8] = $hash2[8] -band 0x3f 9 | $hash2[8] = $hash2[8] -bor 0x80 10 | $newGUID = (new-object -TypeName System.Guid -ArgumentList (,$hash2)).Guid 11 | $fixedGUID = $newGUID.Substring(18) 12 | $scId = $newGUID.Substring(6,2) + $newGUID.Substring(4,2) + $newGUID.Substring(2,2) + $newGUID.Substring(0,2) + "-" + $newGUID.Substring(11,2) + $newGUID.Substring(9,2) + "-" + $newGUID.Substring(16,2) + $newGUID.Substring(14,2) + $fixedGUID 13 | $scId = $scId.Replace("-","") 14 | $scId = "vvol:" + $scId.Insert(16,"-") 15 | write-host $scId -------------------------------------------------------------------------------- /presentVMFScopy.ps1: -------------------------------------------------------------------------------- 1 | Write-Host " __________________________" 2 | Write-Host " /++++++++++++++++++++++++++\" 3 | Write-Host " /++++++++++++++++++++++++++++\" 4 | Write-Host " /++++++++++++++++++++++++++++++\" 5 | Write-Host " /++++++++++++++++++++++++++++++++\" 6 | Write-Host " /++++++++++++++++++++++++++++++++++\" 7 | Write-Host " /++++++++++++/----------\++++++++++++\" 8 | Write-Host " /++++++++++++/ \++++++++++++\" 9 | Write-Host " /++++++++++++/ \++++++++++++\" 10 | Write-Host " /++++++++++++/ \++++++++++++\" 11 | Write-Host " /++++++++++++/ \++++++++++++\" 12 | Write-Host " \++++++++++++\ /++++++++++++/" 13 | Write-Host " \++++++++++++\ /++++++++++++/" 14 | Write-Host " \++++++++++++\ /++++++++++++/" 15 | Write-Host " \++++++++++++\ /++++++++++++/" 16 | Write-Host " \++++++++++++\ /++++++++++++/" 17 | Write-Host " \++++++++++++\" 18 | Write-Host " \++++++++++++\" 19 | Write-Host " \++++++++++++\" 20 | Write-Host " \++++++++++++\" 21 | Write-Host " \------------\" 22 | Write-Host 23 | Write-host "Pure Storage VMware Volume Refresh Script v1.1" 24 | write-host "----------------------------------------------" 25 | write-host 26 | <# 27 | 28 | Written by Cody Hosterman www.codyhosterman.com 29 | 30 | *******Disclaimer:****************************************************** 31 | This scripts are offered "as is" with no warranty. While this 32 | scripts is tested and working in my environment, it is recommended that you test 33 | this script in a test lab before using in a production environment. Everyone can 34 | use the scripts/commands provided here without any written permission but I 35 | will not be liable for any damage or loss to the system. 36 | ************************************************************************ 37 | 38 | This script will take in the source VMFS name and the target VMFS name and refresh the target with the latest snapshot of the source. 39 | Enter in vCenter credentials and one or more FlashArrays. The FlashArrays must use the same credentials. If different credentials 40 | are needed for each FlashArray the script must be altered slightly. 41 | 42 | Supports: 43 | -PowerShell 3.0 or later 44 | -Pure Storage PowerShell SDK 1.0 or later 45 | -PowerCLI 6.0 Release 1 or later (5.5/5.8 is likely fine, but not tested with this script version) 46 | -REST API 1.4 and later 47 | -Purity 4.1 and later 48 | -FlashArray 400 Series and //m 49 | #> 50 | 51 | while ($varsCorrect -ne "Y") 52 | { 53 | $flasharrays = @() 54 | $arraycount = Read-Host "How many FlashArrays do you want to search? [Enter a whole number 1 or higher]" 55 | Write-Host "Please enter each FlashArray FQDN or IP one at a time and press enter after each entry" 56 | for ($faentry=1; $faentry -le $arraycount; $faentry++) 57 | { 58 | $flasharrays += Read-Host "Enter FlashArray FQDN or IP" 59 | } 60 | $pureuser = Read-Host "Enter FlashArray user name" 61 | $pureuserpwd = Read-Host "Enter FlashArray password" -AsSecureString 62 | $vcenter = Read-Host "Enter vCenter FQDN or IP" 63 | $vcuser = Read-Host "Enter vCenter user name" 64 | $vcpass = Read-Host "Enter vCenter password" -AsSecureString 65 | $vmfsname = Read-Host "Enter Source VMFS Name" 66 | $recoveryvmfsname = Read-Host "Enter Recovery VMFS Name" 67 | write-host "" 68 | $varsCorrect = read-host "Are the above values entered accurately? Y/N" 69 | } 70 | 71 | $FACreds = New-Object System.Management.Automation.PSCredential ($pureuser, $pureuserpwd) 72 | $VCCreds = New-Object System.Management.Automation.PSCredential ($vcuser, $vcpass) 73 | 74 | #A function to rescan all of the input ESXi servers and rescan for VMFS volumes. This starts all host operations in parallel and waits for them all to complete. Is called throughout as needed 75 | function rescanESXiHosts 76 | { 77 | foreach ($esxi in $hosts) 78 | { 79 | $argList = @($vcenter, $VCCreds, $esxi) 80 | $job = Start-Job -ScriptBlock{ 81 | Connect-VIServer -Server $args[0] -Credential $args[1] 82 | Get-VMHost -Name $args[2] | Get-VMHostStorage -RescanAllHba -RescanVMFS 83 | Disconnect-VIServer -Confirm:$false 84 | } -ArgumentList $argList 85 | } 86 | Get-Job | Wait-Job |out-null 87 | } 88 | 89 | #A function to unmount and detach the VMFS from all of the input ESXi servers. This starts all host operations in parallel and waits for them all to complete. Is called throughout as needed 90 | function unmountandDetachVMFS 91 | { 92 | foreach ($esxi in $hosts) 93 | { 94 | $argList = @($vcenter, $VCCreds, $esxi, $recoverydatastore) 95 | $job = Start-Job -ScriptBlock{ 96 | Connect-VIServer -Server $args[0] -Credential $args[1] 97 | $esxihost = get-vmhost $args[2] 98 | $datastore = get-datastore $args[3] 99 | $storageSystem = Get-View $esxihost.Extensiondata.ConfigManager.StorageSystem 100 | $StorageSystem.UnmountVmfsVolume($datastore.ExtensionData.Info.vmfs.uuid) |out-null 101 | $storageSystem.DetachScsiLun((Get-ScsiLun -VmHost $esxihost | where {$_.CanonicalName -eq $datastore.ExtensionData.Info.Vmfs.Extent.DiskName}).ExtensionData.Uuid) |out-null 102 | Disconnect-VIServer -Confirm:$false 103 | } -ArgumentList $argList 104 | } 105 | Get-Job | Wait-Job |out-null 106 | } 107 | #Connect to each FlashArray and get all of their volume information 108 | $facount = 0 109 | $purevolumes=@() 110 | $EndPoint= @() 111 | foreach ($flasharray in $flasharrays) 112 | { 113 | if ($facount -eq 0) 114 | { 115 | $EndPoint = @(New-PfaArray -EndPoint $flasharray -Credentials $FACreds -IgnoreCertificateError) 116 | $purevolumes += Get-PfaVolumes -Array $EndPoint[$facount] 117 | $tempvols = @(Get-PfaVolumes -Array $EndPoint[$facount]) 118 | $arraysnlist = @($tempvols.serial[0].substring(0,16)) 119 | } 120 | else 121 | { 122 | $EndPoint += New-PfaArray -EndPoint $flasharray -Credentials $FACreds -IgnoreCertificateError 123 | $purevolumes += Get-PfaVolumes -Array $EndPoint[$facount] 124 | $tempvols = Get-PfaVolumes -Array $EndPoint[$facount] 125 | $arraysnlist += $tempvols.serial[0].substring(0,16) 126 | } 127 | $facount = $facount + 1 128 | } 129 | #Connect to vCenter 130 | Set-PowerCLIConfiguration -DisplayDeprecationWarnings:$false -confirm:$false| Out-Null 131 | connect-viserver -Server $vcenter -Credential $VCCreds|out-null 132 | write-host "*****************************************************************" 133 | write-host "" 134 | 135 | #Find which FlashArray the source VMFS volume is on 136 | $datastore = get-datastore $vmfsname 137 | $lun = $datastore.ExtensionData.Info.Vmfs.Extent.DiskName 138 | if ($lun -like 'naa.624a9370*') 139 | { 140 | $volserial = ($lun.ToUpper()).substring(12) 141 | $purevol = $purevolumes | where-object { $_.serial -eq $volserial } 142 | for($i=0; $i -lt $arraysnlist.count; $i++) 143 | { 144 | if ($arraysnlist[$i] -eq ($volserial.substring(0,16))) 145 | { 146 | $sourcearraychoice = $i 147 | } 148 | } 149 | write-host ("The source VMFS named " + $vmfsname + " is on a FlashArray named " + $EndPoint[$sourcearraychoice].EndPoint) 150 | write-host ("The source FlashArray volume is named " + $purevol.name) 151 | if ($purevol -ne $null) 152 | { 153 | $volumeexists = 1 154 | } 155 | else 156 | { 157 | write-host "The target volume is a FlashArray volume, but not on one of the entered arrays" 158 | } 159 | } 160 | else 161 | { 162 | write-host 'The source datastore is NOT a FlashArray volume.' 163 | } 164 | 165 | #Find which FlashArray the target VMFS volume is on 166 | $recoverydatastore = get-datastore $recoveryvmfsname 167 | $recoverylun = $recoverydatastore.ExtensionData.Info.Vmfs.Extent.DiskName 168 | if ($recoverylun -like 'naa.624a9370*') 169 | { 170 | $recoveryvolserial = ($recoverylun.ToUpper()).substring(12) 171 | $recoverypurevol = $purevolumes | where-object { $_.serial -eq $recoveryvolserial } 172 | for($i=0; $i -lt $arraysnlist.count; $i++) 173 | { 174 | if ($arraysnlist[$i] -eq ($recoveryvolserial.substring(0,16))) 175 | { 176 | $targetarraychoice = $i 177 | } 178 | } 179 | write-host ("The target VMFS named " + $recoveryvmfsname + " is on a FlashArray named " + $EndPoint[$targetarraychoice].EndPoint) 180 | write-host ("The target FlashArray volume is named " + $recoverypurevol.name) 181 | if ($recoverypurevol -ne $null) 182 | { 183 | $recoveryvolumeexists = 1 184 | } 185 | else 186 | { 187 | write-host "The target volume is a FlashArray volume, but not on one of the entered arrays" 188 | } 189 | } 190 | else 191 | { 192 | write-host 'The target datastore is NOT a FlashArray volume.' 193 | } 194 | 195 | if (($volumeexists -eq 1) -and ($recoveryvolumeexists -eq 1)) 196 | { 197 | $vms = $recoverydatastore |get-vm 198 | #Make sure there are no VMs on the recovery VMFS 199 | if ($vms.count -ge 1) 200 | { 201 | write-host ("There are VMs registered to the recovery datastore. Unregister them first then re-run this script.") 202 | } 203 | else 204 | { 205 | $hosts = $recoverydatastore |get-vmhost 206 | write-host "Unmounting and detaching the VMFS volume from the following ESXi hosts:" 207 | write-host $hosts 208 | unmountandDetachVMFS 209 | <# Does the following: 210 | 1) Get the latest FlashArray snapshot from the source VMFS. 211 | 2) Gets any host groups and host the recovery volume is connected to 212 | 3) Removes the recovery volume from the hosts and/or host groups 213 | 4) Deletes the volume 214 | 5) Creates a new volume with the same name from the latest snapshot of the source 215 | 6) Adds the volume back to all of its hosts and host groups 216 | #> 217 | rescanESXiHosts |out-null 218 | $esxcli = $hosts[0] | get-esxcli 219 | if ($EndPoint[$sourcearraychoice].EndPoint -eq $EndPoint[$targetarraychoice].EndPoint) 220 | { 221 | $snapshots = Get-PfaVolumeSnapshots -Array $EndPoint[$targetarraychoice] -VolumeName $purevol.name 222 | } 223 | if ($EndPoint[$sourcearraychoice].EndPoint -ne $EndPoint[$targetarraychoice].EndPoint) 224 | { 225 | $snaps = Get-PfaallVolumeSnapshots -array $EndPoint[$targetarraychoice] 226 | $sourcearray = Get-PfaArrayAttributes -array $EndPoint[$sourcearraychoice] 227 | $fullpurevolname = $sourcearray.array_name+":"+$purevol.name 228 | $snapshots = @() 229 | foreach ($snap in $snaps) 230 | { 231 | if ($snap.source -eq $fullpurevolname) 232 | { 233 | $snapshots += $snap 234 | } 235 | } 236 | } 237 | $fahosts = Get-PfaVolumeHostConnections -Array $EndPoint[$targetarraychoice] -VolumeName $recoverypurevol.name 238 | $fahosts = $fahosts.host 239 | $fahostgroups = Get-PfaVolumeHostGroupConnections -Array $EndPoint[$targetarraychoice] -VolumeName $recoverypurevol.name 240 | $fahostgroups = $fahostgroups.hgroup |get-unique 241 | if ($fahosts.count -ge 1) 242 | { 243 | write-host "The volume is presented privately to the following hosts:" 244 | write-host $fahosts 245 | write-host "Removing the volume from the host(s)..." -foregroundcolor "red" 246 | foreach($fahost in $fahosts) 247 | { 248 | Remove-PfaHostVolumeConnection -Array $EndPoint[$targetarraychoice] -VolumeName $recoverypurevol.name -HostName $fahost |out-null 249 | } 250 | } 251 | if ($fahostgroups.count -ge 1) 252 | { 253 | write-host "The volume is presented to the following host groups:" 254 | write-host $fahostgroups 255 | write-host "Removing the volume from the host groups(s)..." -foregroundcolor "red" 256 | foreach($fahostgroup in $fahostgroups) 257 | { 258 | Remove-PfaHostGroupVolumeConnection -Array $EndPoint[$targetarraychoice] -VolumeName $recoverypurevol.name -HostGroupName $fahostgroup |out-null 259 | } 260 | } 261 | write-host "Deleting and permanently eradicating the volume named" $recoverypurevol.name -foregroundcolor "red" 262 | Remove-PfaVolumeOrSnapshot -Array $EndPoint[$targetarraychoice] -Name $recoverypurevol.name -Confirm:$false |out-null 263 | Remove-PfaVolumeOrSnapshot -Array $EndPoint[$targetarraychoice] -Name $recoverypurevol.name -Confirm:$false -Eradicate |out-null 264 | $newvol = New-PfaVolume -Array $EndPoint[$targetarraychoice] -VolumeName $recoverypurevol.name -Source $snapshots[0].name 265 | write-host "Created a new volume with the name" $recoverypurevol.name "from the snapshot" $snapshots[0].name -foregroundcolor "green" 266 | if ($fahosts.count -ge 1) 267 | { 268 | write-host "Adding the new volume back privately to the following host(s)" -foregroundcolor "green" 269 | write-host $fahosts 270 | foreach($fahost in $fahosts) 271 | { 272 | New-PfaHostVolumeConnection -Array $EndPoint[$targetarraychoice] -VolumeName $recoverypurevol.name -HostName $fahost |out-null 273 | } 274 | } 275 | if ($fahostgroups.count -ge 1) 276 | { 277 | write-host "Adding the new volume back to the following host group(s)" -foregroundcolor "green" 278 | write-host $fahostgroups 279 | foreach($fahostgroup in $fahostgroups) 280 | { 281 | New-PfaHostGroupVolumeConnection -Array $EndPoint[$targetarraychoice] -VolumeName $recoverypurevol.name -HostGroupName $fahostgroup |out-null 282 | } 283 | } 284 | Start-Sleep -s 30 285 | rescanESXiHosts 286 | #resignatures the datastore after a rescan 287 | Start-sleep -s 10 288 | $unresolvedvmfs = $esxcli.storage.vmfs.snapshot.list($vmfsname) 289 | $recoverylun = ("naa.624a9370" + $newvol.serial) 290 | if ($unresolvedvmfs.UnresolvedExtentCount -ge 2) 291 | { 292 | write-host "ERROR: There are more than one unresolved copies of the source VMFS named" $vmfsname -foregroundcolor "red" 293 | write-host "Please remove the additional copies in order to mount" 294 | } 295 | else 296 | { 297 | write-host "Resignaturing the VMFS on the device" $recoverylun "and then mounting it..." 298 | $esxcli.storage.vmfs.snapshot.resignature($vmfsname) |out-null 299 | rescanESXiHosts 300 | $datastores = $hosts[0] | Get-Datastore 301 | #renames the VMFS back to the original recovery name 302 | foreach ($ds in $datastores) 303 | { 304 | $naa = $ds.ExtensionData.Info.Vmfs.Extent.DiskName 305 | if ($naa -eq $recoverylun.ToLower()) 306 | { 307 | $resigds = $ds 308 | } 309 | } 310 | $resigds | Set-Datastore -Name $recoveryvmfsname |out-null 311 | write-host "Renaming the datastore" $resigds.name "back to" $recoveryvmfsname 312 | } 313 | 314 | } 315 | } 316 | #disconnecting sessions 317 | disconnect-viserver -Server $vcenter -confirm:$false 318 | foreach ($flasharray in $endpoint) 319 | { 320 | Disconnect-PfaArray -Array $flasharray 321 | } -------------------------------------------------------------------------------- /protectiongrouprecovery.ps1: -------------------------------------------------------------------------------- 1 | <#*************************************************************************************************** 2 | Requires only PowerShell 2.0 or later 3 | *************************************************************************************************** 4 | 5 | For info, refer to www.codyhosterman.com 6 | 7 | ***************************************************************** 8 | Enter the following parameters. 9 | Put all entries inside the quotes except $overwrite, that should be $true or $false 10 | *****************************************************************#> 11 | $flasharray = "" 12 | $pureuser = "" 13 | $purepass = "" 14 | $overwrite = $false 15 | $snapshot = "" 16 | $pgrouptarget = "" 17 | #***************************************************************** 18 | 19 | <# 20 | *******Disclaimer:****************************************************** 21 | This scripts are offered "as is" with no warranty. While this 22 | scripts is tested and working in my environment, it is recommended that you test 23 | this script in a test lab before using in a production environment. Everyone can 24 | use the scripts/commands provided here without any written permission but I 25 | will not be liable for any damage or loss to the system. 26 | ************************************************************************ 27 | 28 | This script will recover respective volumes in a FlashArray protection group from a specified snapshot 29 | 30 | Supports: 31 | -PowerShell 2.0 or later 32 | -REST API 1.5 and later 33 | -Purity 4.6 and later 34 | -FlashArray 400 Series and //m 35 | #> 36 | 37 | write-host ' __________________________' 38 | write-host ' /++++++++++++++++++++++++++\' 39 | write-host ' /++++++++++++++++++++++++++++\' 40 | write-host ' /++++++++++++++++++++++++++++++\' 41 | write-host ' /++++++++++++++++++++++++++++++++\' 42 | write-host ' /++++++++++++++++++++++++++++++++++\' 43 | write-host ' /++++++++++++/----------\++++++++++++\' 44 | write-host ' /++++++++++++/ \++++++++++++\' 45 | write-host ' /++++++++++++/ \++++++++++++\' 46 | write-host ' /++++++++++++/ \++++++++++++\' 47 | write-host ' /++++++++++++/ \++++++++++++\' 48 | write-host ' \++++++++++++\ /++++++++++++/' 49 | write-host ' \++++++++++++\ /++++++++++++/' 50 | write-host ' \++++++++++++\ /++++++++++++/' 51 | write-host ' \++++++++++++\ /++++++++++++/' 52 | write-host ' \++++++++++++\ /++++++++++++/' 53 | write-host ' \++++++++++++\' 54 | write-host ' \++++++++++++\' 55 | write-host ' \++++++++++++\' 56 | write-host ' \++++++++++++\' 57 | write-host ' \------------\' 58 | write-host 'Pure Storage Protection Group Recovery v1.0' 59 | write-host '----------------------------------------------------------------------------------------------------' 60 | 61 | [System.Net.ServicePointManager]::ServerCertificateValidationCallback = { $true } 62 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 63 | $AuthAction = @{ 64 | password = ${pureuser} 65 | username = ${purepass} 66 | } 67 | try 68 | { 69 | write-host ("Connecting to the FlashArray at " + $flasharray +"...") 70 | $ApiToken = Invoke-RestMethod -Method Post -Uri "https://$flasharray/api/1.5/auth/apitoken" -Body $AuthAction -ErrorAction stop 71 | $SessionAction = @{ 72 | api_token = $ApiToken.api_token 73 | } 74 | Invoke-RestMethod -Method Post -Uri "https://${flasharray}/api/1.5/auth/session" -Body $SessionAction -SessionVariable Session -ErrorAction stop |out-null 75 | write-host "Connection successful!" -ForegroundColor Green 76 | } 77 | catch 78 | { 79 | write-host 80 | write-host "Connection failed!" -backgroundColor Red 81 | write-host 82 | write-host $error[0] -ForegroundColor Red 83 | write-host 84 | return 85 | } 86 | $restbody = [ordered]@{ 87 | source = $snapshot 88 | overwrite = $overwrite 89 | } | ConvertTo-Json 90 | try 91 | { 92 | write-host 93 | write-host ("Recovering the protection group " + $result.name + " from the specified Point-In-Time $snapshot...") 94 | $result = Invoke-RestMethod -Method Post -Uri "https://${flasharray}/api/1.5/pgroup/$pgrouptarget" -Body $restbody -WebSession $Session -ContentType "application/json" -ErrorAction stop 95 | write-host ("The protection group recovery on group " + $result.name + " succeeded from Point-In-Time $snapshot") -ForegroundColor Green 96 | } 97 | catch 98 | { 99 | write-host 100 | write-host "Recovery failed!" -backgroundColor Red 101 | write-host 102 | write-host $error[0] -ForegroundColor Red 103 | write-host 104 | } 105 | Invoke-RestMethod -Method Delete -Uri "https://${flasharray}/api/1.5/auth/session" -WebSession $Session|out-null 106 | 107 | -------------------------------------------------------------------------------- /unattendedUnmapConfigurator.ps1: -------------------------------------------------------------------------------- 1 | #For use with the Unattended UNMAP Script found here: https://github.com/codyhosterman/powercli/blob/master/unmapsdkunattended.ps1 2 | <#*******Disclaimer:****************************************************** 3 | This scripts are offered "as is" with no warranty. While this 4 | scripts is tested and working in my environment, it is recommended that you test 5 | this script in a test lab before using in a production environment. Everyone can 6 | use the scripts/commands provided here without any written permission but I 7 | will not be liable for any damage or loss to the system. 8 | ************************************************************************#> 9 | 10 | write-host ' __________________________' 11 | write-host ' /++++++++++++++++++++++++++\' 12 | write-host ' /++++++++++++++++++++++++++++\' 13 | write-host ' /++++++++++++++++++++++++++++++\' 14 | write-host ' /++++++++++++++++++++++++++++++++\' 15 | write-host ' /++++++++++++++++++++++++++++++++++\' 16 | write-host ' /++++++++++++/----------\++++++++++++\' 17 | write-host ' /++++++++++++/ \++++++++++++\' 18 | write-host ' /++++++++++++/ \++++++++++++\' 19 | write-host ' /++++++++++++/ \++++++++++++\' 20 | write-host ' /++++++++++++/ \++++++++++++\' 21 | write-host ' \++++++++++++\ /++++++++++++/' 22 | write-host ' \++++++++++++\ /++++++++++++/' 23 | write-host ' \++++++++++++\ /++++++++++++/' 24 | write-host ' \++++++++++++\ /++++++++++++/' 25 | write-host ' \++++++++++++\ /++++++++++++/' 26 | write-host ' \++++++++++++\' 27 | write-host ' \++++++++++++\' 28 | write-host ' \++++++++++++\' 29 | write-host ' \++++++++++++\' 30 | write-host ' \------------\' 31 | write-host 'Pure Storage FlashArray VMware ESXi UNMAP Credential Configurator v1.0' 32 | write-host '----------------------------------------------------------------------------------------------------' 33 | write-host '' 34 | write-host '' 35 | write-host "Please choose a directory to store the encrypted credential files" 36 | function ChooseFolder([string]$Message, [string]$InitialDirectory) 37 | { 38 | $app = New-Object -ComObject Shell.Application 39 | $folder = $app.BrowseForFolder(0, $Message, 0, $InitialDirectory) 40 | $selectedDirectory = $folder.Self.Path 41 | return $selectedDirectory 42 | } 43 | try 44 | { 45 | $credentialfolder = ChooseFolder -Message "Please select a credential file directory" -InitialDirectory 'MyComputer' 46 | $fapath = join-path -path $credentialfolder -childpath "faUnmapCreds.xml" 47 | $Host.ui.PromptForCredential("Need FlashArray Credentials", "Please enter your FlashArray username and password.", "","") | Export-Clixml -Path $fapath 48 | $vcpath = join-path -path $credentialfolder -childpath "vcUnmapCreds.xml" 49 | $Host.ui.PromptForCredential("Need vCenter Credentials", "Please enter your vCenter username and password.", "","") | Export-Clixml -Path $vcpath 50 | write-host "Created credential files successfully" 51 | } 52 | catch 53 | { 54 | write-host $Error[0] 55 | } 56 | -------------------------------------------------------------------------------- /unmapsdk.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | *************************************************************************************************** 3 | VMWARE POWERCLI AND PURE STORAGE POWERSHELL SDK MUST BE INSTALLED ON THE MACHINE THIS IS RUNNING ON 4 | *************************************************************************************************** 5 | 6 | For info, refer to www.codyhosterman.com 7 | 8 | *******Disclaimer:****************************************************** 9 | This scripts are offered "as is" with no warranty. While this 10 | scripts is tested and working in my environment, it is recommended that you test 11 | this script in a test lab before using in a production environment. Everyone can 12 | use the scripts/commands provided here without any written permission but I 13 | will not be liable for any damage or loss to the system. 14 | ************************************************************************ 15 | 16 | This script will identify Pure Storage FlashArray volumes and issue UNMAP against them. The script uses the best practice 17 | recommendation block count of 1% of the free capacity of the datastore. All operations are logged to a file and also 18 | output to the screen. REST API calls to the array before and after UNMAP will report on how much (if any) space has been reclaimed. 19 | 20 | This can be run directly from PowerCLI or from a standard PowerShell prompt. PowerCLI must be installed on the local host regardless. 21 | 22 | Supports: 23 | -PowerShell 3.0 or later 24 | -Pure Storage PowerShell SDK 1.5 or later 25 | -PowerCLI 6.3 Release 1+ 26 | -REST API 1.4 and later 27 | -Purity 4.1 and later 28 | -FlashArray 400 Series and //m 29 | -vCenter 5.5 and later 30 | -Each FlashArray datastore must be present to at least one ESXi version 5.5 or later host or it will not be reclaimed 31 | #> 32 | 33 | #Create log folder if non-existent 34 | write-host "Please choose a directory to store the script log" 35 | function ChooseFolder([string]$Message, [string]$InitialDirectory) 36 | { 37 | $app = New-Object -ComObject Shell.Application 38 | $folder = $app.BrowseForFolder(0, $Message, 0, $InitialDirectory) 39 | $selectedDirectory = $folder.Self.Path 40 | return $selectedDirectory 41 | } 42 | $logfolder = ChooseFolder -Message "Please select a log file directory" -InitialDirectory 'MyComputer' 43 | $logfile = $logfolder + '\' + (Get-Date -Format o |ForEach-Object {$_ -Replace ':', '.'}) + "unmapresults.txt" 44 | write-host "Script result log can be found at $logfile" -ForegroundColor Green 45 | 46 | #Configure optional Log Insight target 47 | $useloginsight = read-host "Would you like to send the UNMAP results to a Log Insight instance (y/n)" 48 | while (($useloginsight -ine "y") -and ($useloginsight -ine "n")) 49 | { 50 | write-host "Invalid entry, please enter y or n" 51 | $useloginsight = read-host "Would you like to send the UNMAP results to a Log Insight instance (y/n)" 52 | } 53 | if ($useloginsight -ieq "y") 54 | { 55 | $loginsightserver = read-host "Enter in the FQDN or IP of Log Insight" 56 | $loginsightagentID = read-host "Enter a Log Insight Agent ID" 57 | add-content $logfile ('Results will be sent to the following Log Insight instance ' + $loginsightserver + ' with the UUID of ' + $loginsightagentID) 58 | } 59 | elseif ($useloginsight -ieq "n") 60 | { 61 | add-content $logfile ('Log Insight will not be used for external logging') 62 | } 63 | 64 | #Import PowerCLI. Requires PowerCLI version 6.3 or later. Will fail here if PowerCLI is not installed 65 | if ( !(Get-Module -Name VMware.VimAutomation.Core -ErrorAction SilentlyContinue) ) { 66 | if (Test-Path “C:\Program Files (x86)\VMware\Infrastructure\PowerCLI\Scripts\Initialize-PowerCLIEnvironment.ps1”) 67 | { 68 | . “C:\Program Files (x86)\VMware\Infrastructure\PowerCLI\Scripts\Initialize-PowerCLIEnvironment.ps1” |out-null 69 | } 70 | elseif (Test-Path “C:\Program Files (x86)\VMware\Infrastructure\vSphere PowerCLI\Scripts\Initialize-PowerCLIEnvironment.ps1”) 71 | { 72 | . “C:\Program Files (x86)\VMware\Infrastructure\vSphere PowerCLI\Scripts\Initialize-PowerCLIEnvironment.ps1” |out-null 73 | } 74 | if ( !(Get-Module -Name VMware.VimAutomation.Core -ErrorAction SilentlyContinue) ) 75 | { 76 | write-host ("PowerCLI not found. Please verify installation and retry.") -BackgroundColor Red 77 | write-host "Terminating Script" -BackgroundColor Red 78 | add-content $logfile ("PowerCLI not found. Please verify installation and retry.") 79 | add-content $logfile "Get it here: https://my.vmware.com/web/vmware/details?downloadGroup=PCLI650R1&productId=614" 80 | add-content $logfile "Terminating Script" 81 | return 82 | } 83 | 84 | } 85 | #Set 86 | Set-PowerCLIConfiguration -invalidcertificateaction "ignore" -confirm:$false |out-null 87 | Set-PowerCLIConfiguration -Scope Session -WebOperationTimeoutSeconds -1 -confirm:$false |out-null 88 | 89 | if ( !(Get-Module -ListAvailable -Name PureStoragePowerShellSDK -ErrorAction SilentlyContinue) ) { 90 | write-host ("FlashArray PowerShell SDK not found. Please verify installation and retry.") -BackgroundColor Red 91 | write-host "Get it here: https://github.com/PureStorage-Connect/PowerShellSDK" 92 | write-host "Terminating Script" -BackgroundColor Red 93 | add-content $logfile ("FlashArray PowerShell SDK not found. Please verify installation and retry.") 94 | add-content $logfile "Get it here: https://github.com/PureStorage-Connect/PowerShellSDK" 95 | add-content $logfile "Terminating Script" 96 | return 97 | } 98 | write-host ' __________________________' 99 | write-host ' /++++++++++++++++++++++++++\' 100 | write-host ' /++++++++++++++++++++++++++++\' 101 | write-host ' /++++++++++++++++++++++++++++++\' 102 | write-host ' /++++++++++++++++++++++++++++++++\' 103 | write-host ' /++++++++++++++++++++++++++++++++++\' 104 | write-host ' /++++++++++++/----------\++++++++++++\' 105 | write-host ' /++++++++++++/ \++++++++++++\' 106 | write-host ' /++++++++++++/ \++++++++++++\' 107 | write-host ' /++++++++++++/ \++++++++++++\' 108 | write-host ' /++++++++++++/ \++++++++++++\' 109 | write-host ' \++++++++++++\ /++++++++++++/' 110 | write-host ' \++++++++++++\ /++++++++++++/' 111 | write-host ' \++++++++++++\ /++++++++++++/' 112 | write-host ' \++++++++++++\ /++++++++++++/' 113 | write-host ' \++++++++++++\ /++++++++++++/' 114 | write-host ' \++++++++++++\' 115 | write-host ' \++++++++++++\' 116 | write-host ' \++++++++++++\' 117 | write-host ' \++++++++++++\' 118 | write-host ' \------------\' 119 | write-host 'Pure Storage FlashArray VMware ESXi UNMAP Script v5.0' 120 | write-host '----------------------------------------------------------------------------------------------------' 121 | 122 | $FAcount = 0 123 | $inputOK = $false 124 | do 125 | { 126 | try 127 | { 128 | [int]$FAcount = Read-Host "How many FlashArrays do you need to enter (enter a number)" 129 | $inputOK = $true 130 | } 131 | catch 132 | { 133 | Write-Host -ForegroundColor red "INVALID INPUT! Please enter a numeric value." 134 | } 135 | } 136 | until ($inputOK) 137 | $flasharrays = @() 138 | for ($i=0;$i -lt $FAcount;$i++) 139 | { 140 | $flasharray = read-host "Please enter a FlashArray IP or FQDN" 141 | $flasharrays += $flasharray 142 | } 143 | $Creds = $Host.ui.PromptForCredential("FlashArray Credentials", "Please enter your FlashArray username and password.", "","") 144 | #Connect to FlashArray via REST 145 | $purevolumes=@() 146 | $purevol=$null 147 | $EndPoint= @() 148 | $arraysnlist = @() 149 | 150 | <# 151 | Connect to FlashArray via REST with the SDK 152 | Assumes the same credentials are in use for every FlashArray 153 | #> 154 | 155 | foreach ($flasharray in $flasharrays) 156 | { 157 | try 158 | { 159 | $tempArray = (New-PfaArray -EndPoint $flasharray -Credentials $Creds -IgnoreCertificateError -ErrorAction stop) 160 | $EndPoint += $temparray 161 | $purevolumes += Get-PfaVolumes -Array $tempArray 162 | $arraySN = Get-PfaArrayAttributes -Array $tempArray 163 | 164 | if ($arraySN.id[0] -eq "0") 165 | { 166 | $arraySN = $arraySN.id.Substring(1) 167 | $arraySN = $arraySN.substring(0,19) 168 | } 169 | else 170 | { 171 | $arraySN = $arraySN.id.substring(0,18) 172 | } 173 | $arraySN = $arraySN -replace '-','' 174 | $arraysnlist += $arraySN 175 | add-content $logfile "FlashArray shortened serial is $($arraySN)" 176 | } 177 | catch 178 | { 179 | add-content $logfile "" 180 | add-content $logfile ("Connection to FlashArray " + $flasharray + " failed. Please check credentials or IP/FQDN") 181 | add-content $logfile $Error[0] 182 | add-content $logfile "Terminating Script" 183 | return 184 | } 185 | } 186 | 187 | add-content $logfile '----------------------------------------------------------------------------------------------------' 188 | add-content $logfile 'Connected to the following FlashArray(s):' 189 | add-content $logfile $flasharrays 190 | add-content $logfile '----------------------------------------------------------------------------------------------------' 191 | write-host "" 192 | $vcenter = read-host "Please enter a vCenter IP or FQDN" 193 | $newcreds = Read-host "Re-use the FlashArray credentials for vCenter? (y/n)" 194 | while (($newcreds -ine "y") -and ($newcreds -ine "n")) 195 | { 196 | write-host "Invalid entry, please enter y or n" 197 | $newcreds = Read-host "Re-use the FlashArray credentials for vCenter? (y/n)" 198 | } 199 | if ($newcreds -ieq "n") 200 | { 201 | $Creds = $Host.ui.PromptForCredential("vCenter Credentials", "Please enter your vCenter username and password.", "","") 202 | } 203 | try 204 | { 205 | connect-viserver -Server $vcenter -Credential $Creds -ErrorAction Stop |out-null 206 | add-content $logfile ('Connected to the following vCenter:') 207 | add-content $logfile $vcenter 208 | add-content $logfile '----------------------------------------------------------------------------------------------------' 209 | } 210 | catch 211 | { 212 | write-host "Failed to connect to vCenter" -BackgroundColor Red 213 | write-host $vcenter 214 | write-host $Error[0] 215 | write-host "Terminating Script" -BackgroundColor Red 216 | add-content $logfile "Failed to connect to vCenter" 217 | add-content $logfile $vcenter 218 | add-content $logfile $Error[0] 219 | add-content $logfile "Terminating Script" 220 | return 221 | } 222 | write-host "" 223 | 224 | #A function to make REST Calls to Log Insight 225 | function logInsightRestCall 226 | { 227 | $restvmfs = [ordered]@{ 228 | name = "Datastore" 229 | content = $datastore.Name 230 | } 231 | $restarray = [ordered]@{ 232 | name = "FlashArray" 233 | content = $endpoint[$arraychoice].endpoint 234 | } 235 | $restvol = [ordered]@{ 236 | name = "FlashArrayvol" 237 | content = $purevol.name 238 | } 239 | $restunmap = [ordered]@{ 240 | name = "ReclaimedSpaceGB" 241 | content = $reclaimedvirtualspace 242 | } 243 | $esxhost = [ordered]@{ 244 | name = "ESXihost" 245 | content = $esxchosen[$i].name 246 | } 247 | $devicenaa = [ordered]@{ 248 | name = "SCSINaa" 249 | content = $lun 250 | } 251 | $fields = @($restvmfs,$restarray,$restvol,$restunmap,$esxhost,$devicenaa) 252 | $restcall = @{ 253 | messages = ([Object[]]($messages = [ordered]@{ 254 | text = ("Completed an UNMAP operation on the VMFS volume named " + $datastore.Name + " that is on the FlashArray named " + $endpoint[$arraychoice].endpoint + ".") 255 | fields = ([Object[]]$fields) 256 | })) 257 | } |convertto-json -Depth 4 258 | $resturl = ("http://" + $loginsightserver + ":9000/api/v1/messages/ingest/" + $loginsightagentID) 259 | add-content $logfile "" 260 | if($i=0){add-content $logfile ("Posting results to Log Insight server: " + $loginsightserver)} 261 | try 262 | { 263 | $response = Invoke-RestMethod $resturl -Method Post -Body $restcall -ContentType 'application/json' -ErrorAction stop 264 | if($i=0){add-content $logfile "REST Call to Log Insight server successful"} 265 | } 266 | catch 267 | { 268 | add-content $logfile "REST Call failed to Log Insight server" 269 | add-content $logfile $error[0] 270 | add-content $logfile $resturl 271 | } 272 | } 273 | $arrayspacestart = @() 274 | foreach ($flasharray in $endpoint) 275 | { 276 | $arrayspacestart += Get-PfaArraySpaceMetrics -array $flasharray 277 | } 278 | #Gather VMFS Datastores and identify how many are Pure Storage volumes 279 | $reclaimeddatastores = @() 280 | $virtualspace = @() 281 | $physicalspace = @() 282 | $esxchosen = @() 283 | $expectedreturns = @() 284 | $datastores = get-datastore 285 | add-content $logfile 'Found the following datastores:' 286 | add-content $logfile $datastores 287 | add-content $logfile '----------------------------------------------------------------------------------------------------' 288 | 289 | #Starting UNMAP Process on datastores 290 | write-host "Please be patient--this process can take a long time" 291 | $purevol = $null 292 | foreach ($datastore in $datastores) 293 | { 294 | add-content $logfile (get-date) 295 | add-content $logfile ('The datastore named ' + $datastore + ' is being examined') 296 | $esx = $datastore | get-vmhost | where-object {($_.version -like '5.5.*') -or ($_.version -like '6.*')}| where-object {($_.ConnectionState -eq 'Connected')} |Select-Object -last 1 297 | $unmapconfig = "" 298 | if ($datastore.ExtensionData.Info.Vmfs.majorVersion -eq 6) 299 | { 300 | $esxcli=get-esxcli -VMHost $esx -v2 301 | add-content $logfile ("The datastore named " + $datastore.name + " is VMFS version 6. Checking Automatic UNMAP configuration...") 302 | $unmapargs = $esxcli.storage.vmfs.reclaim.config.get.createargs() 303 | $unmapargs.volumelabel = $datastore.name 304 | $unmapconfig = $esxcli.storage.vmfs.reclaim.config.get.invoke($unmapargs) 305 | } 306 | if ($datastore.Type -ne 'VMFS') 307 | { 308 | add-content $logfile ('This volume is not a VMFS volume, it is of type ' + $datastore.Type + ' and cannot be reclaimed. Skipping...') 309 | add-content $logfile '' 310 | add-content $logfile '----------------------------------------------------------------------------------------------------' 311 | } 312 | elseif ($esx.count -eq 0) 313 | { 314 | add-content $logfile ('This datastore has no 5.5 or later hosts to run UNMAP from. Skipping...') 315 | add-content $logfile '' 316 | add-content $logfile '----------------------------------------------------------------------------------------------------' 317 | 318 | } 319 | elseif ($unmapconfig.ReclaimPriority -eq "low") 320 | { 321 | add-content $logfile ('This VMFS has Automatic UNMAP enabled. No need to run a manual reclaim. Skipping...') 322 | add-content $logfile '' 323 | add-content $logfile '----------------------------------------------------------------------------------------------------' 324 | } 325 | else 326 | { 327 | $lun = $datastore.ExtensionData.Info.Vmfs.Extent.DiskName |select-object -unique 328 | if ($lun.count -eq 1) 329 | { 330 | add-content $logfile ("The UUID for this volume is " + $datastore.ExtensionData.Info.Vmfs.Extent.DiskName) 331 | $esxcli=get-esxcli -VMHost $esx -v2 332 | if ($lun -like 'naa.624a9370*') 333 | { 334 | $volserial = ($lun.ToUpper()).substring(12) 335 | $purevol = $purevolumes | where-object { $_.serial -eq $volserial } 336 | if ($purevol.name -eq $null) 337 | { 338 | add-content $logfile 'ERROR: This volume has not been found. Please make sure that all of the FlashArrays presented to this vCenter are entered into this script.' 339 | add-content $logfile '' 340 | add-content $logfile '----------------------------------------------------------------------------------------------------' 341 | continue 342 | 343 | } 344 | else 345 | { 346 | for($i=0; $i -lt $arraysnlist.count; $i++) 347 | { 348 | if ($arraysnlist[$i] -eq ($volserial.substring(0,16))) 349 | { 350 | $arraychoice = $i 351 | } 352 | } 353 | $arrayname = Get-PfaArrayAttributes -array $EndPoint[$arraychoice] 354 | add-content $logfile ('The volume is on the FlashArray ' + $arrayname.array_name) 355 | add-content $logfile ('This datastore is a Pure Storage volume named ' + $purevol.name) 356 | add-content $logfile '' 357 | add-content $logfile ('The ESXi named ' + $esx + ' will run the UNMAP/reclaim operation') 358 | add-content $logfile '' 359 | $volinfo = Get-PfaVolumeSpaceMetrics -Array $EndPoint[$arraychoice] -VolumeName $purevol.name 360 | $usedvolcap = ((1 - $volinfo.thin_provisioning)*$volinfo.size)/1024/1024/1024 361 | $virtualspace += '{0:N0}' -f ($usedvolcap) 362 | $physicalspace += '{0:N0}' -f ($volinfo.volumes/1024/1024/1024) 363 | $usedspace = $datastore.CapacityGB - $datastore.FreeSpaceGB 364 | $deadspace = '{0:N0}' -f ($usedvolcap - $usedspace) 365 | if ($deadspace -lt 0) 366 | { 367 | $deadspace = 0 368 | } 369 | add-content $logfile ('The current used space of this VMFS is ' + ('{0:N0}' -f ($usedspace)) + " GB") 370 | add-content $logfile ('The current used virtual space for its FlashArray volume is approximately ' + ('{0:N0}' -f ($usedvolcap)) + " GB") 371 | $reclaimable = ('{0:N0}' -f ($deadspace)) 372 | if ($reclaimable -like "-*") 373 | { 374 | $reclaimable = 0 375 | } 376 | $expectedreturns += $reclaimable 377 | add-content $logfile ('The minimum reclaimable virtual space for this FlashArray volume is ' + $reclaimable + ' GB') 378 | #Calculating optimal block count. If VMFS is 75% full or more the count must be 200 MB only. Ideal block count is 1% of free space of the VMFS in MB 379 | if ((1 - $datastore.FreeSpaceMB/$datastore.CapacityMB) -ge .75) 380 | { 381 | $blockcount = 200 382 | add-content $logfile 'The volume is 75% or more full so the block count is overridden to 200 MB. This will slow down the reclaim dramatically' 383 | add-content $logfile 'It is recommended to either free up space on the volume or increase the capacity so it is less than 75% full' 384 | add-content $logfile ("The block count in MB will be " + $blockcount) 385 | } 386 | else 387 | { 388 | $blockcount = [math]::floor($datastore.FreeSpaceMB * .008) 389 | add-content $logfile ("The maximum allowed block count for this datastore is " + $blockcount) 390 | } 391 | $unmapargs = $esxcli.storage.vmfs.unmap.createargs() 392 | $unmapargs.volumelabel = $datastore.Name 393 | $unmapargs.reclaimunit = $blockcount 394 | try 395 | { 396 | $reclaimeddatastores += $datastore 397 | $esxchosen += $esx 398 | write-host ("Running UNMAP on VMFS named " + $datastore.Name + "...") 399 | $esxcli.storage.vmfs.unmap.invoke($unmapargs) |out-null 400 | } 401 | catch 402 | { 403 | add-content $logfile "Failed to complete UNMAP to this volume. Most common cause is a PowerCLI timeout which means UNMAP will continue to completion in the background for this VMFS." 404 | add-content $logfile $Error[0] 405 | add-content $logfile "Moving to the next volume" 406 | continue 407 | } 408 | add-content $logfile '' 409 | add-content $logfile '----------------------------------------------------------------------------------------------------' 410 | } 411 | } 412 | else 413 | { 414 | add-content $logfile ('The volume is not a FlashArray device, skipping the UNMAP operation') 415 | add-content $logfile '' 416 | add-content $logfile '----------------------------------------------------------------------------------------------------' 417 | continue 418 | } 419 | } 420 | elseif ($lun.count -gt 1) 421 | { 422 | add-content $logfile ('The volume spans more than one SCSI device, skipping UNMAP operation') 423 | add-content $logfile '' 424 | add-content $logfile '----------------------------------------------------------------------------------------------------' 425 | continue 426 | } 427 | } 428 | } 429 | $arrayspaceend = @() 430 | $arraychanges = @() 431 | $finaldatastores = @() 432 | $totalreclaimedvirtualspace = 0 433 | start-sleep 120 434 | for ($i=0;$i -lt $reclaimeddatastores.count;$i++) 435 | { 436 | $lun = $reclaimeddatastores[$i].ExtensionData.Info.Vmfs.Extent.DiskName |select-object -unique 437 | $volserial = ($lun.ToUpper()).substring(12) 438 | $purevol = $purevolumes | where-object { $_.serial -eq $volserial } 439 | for($a=0; $a -lt $arraysnlist.count; $a++) 440 | { 441 | if ($arraysnlist[$a] -eq ($volserial.substring(0,16))) 442 | { 443 | $arraychoice = $a 444 | } 445 | } 446 | $volinfo = Get-PfaVolumeSpaceMetrics -Array $EndPoint[$arraychoice] -VolumeName $purevol.name 447 | $usedvolcap = '{0:N0}' -f (((1 - $volinfo.thin_provisioning)*$volinfo.size)/1024/1024/1024) 448 | $newphysicalspace = '{0:N0}' -f ($volinfo.volumes/1024/1024/1024) 449 | $reclaimedvirtualspace = $virtualspace[$i] - $usedvolcap 450 | $reclaimedphysicalspace = $physicalspace[$i] - $newphysicalspace 451 | $totalreclaimedvirtualspace += $reclaimedvirtualspace 452 | if ($reclaimedvirtualspace -like "-*") 453 | { 454 | $reclaimedvirtualspace = 0 455 | } 456 | if ($reclaimedphysicalspace -like "-*") 457 | { 458 | $reclaimedphysicalspace = 0 459 | } 460 | $finaldatastores += New-Object psobject -Property @{Datastore=$($reclaimeddatastores[$i].name);Volume=$($purevol.name);ExpectedMinimumVirtualSpaceGBReclaimed=$($expectedreturns[$i]);ActualVirtualSpaceGBReclaimed=$($reclaimedvirtualspace);ActualPhysicalSpaceGBReclaimed=$($reclaimedphysicalspace)} 461 | if ($useloginsight -ieq "y"){logInsightRestCall} 462 | } 463 | 464 | for ($i=0;$i -lt $endpoint.count;$i++) 465 | { 466 | $arrayspaceend += Get-PfaArraySpaceMetrics -array $endpoint[$i] 467 | $physicalspacedifference = ($arrayspacestart[$i].volumes - $arrayspaceend[$i].volumes)/1024/1024/1024 468 | if ($physicalspacedifference -like "-*") 469 | { 470 | $physicalspacedifference = 0 471 | } 472 | if ($totalreclaimedvirtualspace[$i] -like "-*") 473 | { 474 | $virtualspacedifference = 0 475 | } 476 | else 477 | { 478 | $virtualspacedifference = $totalreclaimedvirtualspace[$i] 479 | } 480 | $arraychanges += New-Object psobject -Property @{FlashArray=$($arrayspaceend[$i].hostname);VirtualSpaceGBReclaimed=$('{0:N0}' -f ($virtualspacedifference));PhysicalSpaceGBReclaimed=$('{0:N0}' -f ($physicalspacedifference))} 481 | } 482 | add-content $logfile "FlashArray-level Reclamation Statistics:" 483 | write-host "FlashArray-level Reclamation Statistics:" 484 | write-host ($arraychanges |ft -autosize -Property FlashArray,VirtualSpaceGBReclaimed,PhysicalSpaceGBReclaimed | Out-String ) 485 | $arraychanges|ft -autosize -Property FlashArray,VirtualSpaceGBReclaimed,PhysicalSpaceGBReclaimed | Out-File -FilePath $logfile -Append -Encoding ASCII 486 | 487 | add-content $logfile "Volume-level Reclamation Statistics:" 488 | write-host "Volume-level Reclamation Statistics:" 489 | write-host ($finaldatastores |ft -autosize -Property Datastore,Volume,ExpectedMinimumVirtualSpaceGBReclaimed,ActualVirtualSpaceGBReclaimed,ActualPhysicalSpaceGBReclaimed | Out-String ) 490 | $finaldatastores|ft -autosize -Property Datastore,Volume,ExpectedMinimumVirtualSpaceGBReclaimed,ActualVirtualSpaceGBReclaimed,ActualPhysicalSpaceGBReclaimed | Out-File -FilePath $logfile -Append -Encoding ASCII 491 | 492 | add-content $logfile ("Space reclaim operation for all FlashArray VMFS volumes is complete.") 493 | add-content $logfile "" 494 | #disconnecting sessions 495 | add-content $logfile ("Disconnecting vCenter and FlashArray sessions") 496 | disconnect-viserver -Server $vcenter -confirm:$false 497 | foreach ($flasharray in $endpoint) 498 | { 499 | Disconnect-PfaArray -Array $flasharray 500 | } -------------------------------------------------------------------------------- /unmapsdkPowerActions.ps1: -------------------------------------------------------------------------------- 1 | param 2 | ( 3 | [Parameter(Mandatory=$true)] 4 | [VMware.VimAutomation.ViCore.Types.V1.Inventory.Cluster] 5 | $cluster 6 | ); 7 | #*************************************************************************************************** 8 | # **********SPECIAL POWERACTIONS VERSION!!!!*********** 9 | # **********ONLY FOR USE WITH VMWARE POWERACTIONS****** 10 | # 11 | # 12 | #VMWARE POWERCLI AND PURE STORAGE POWERSHELL SDK MUST BE INSTALLED ON THE MACHINE THIS IS RUNNING ON 13 | #*************************************************************************************************** 14 | # 15 | #For info, refer to www.codyhosterman.com 16 | # 17 | #***************************************************************** 18 | #Enter the following parameters. Put all entries inside the quotes. 19 | #One or more FlashArrays are supported. Remove/add additional ,''s for more/less arrays. 20 | #Remove '' and replace that entire string with a FlashArray IP or FQDN like '192.168.0.10'. Separate each array by a comma. 21 | #***************************************************************** 22 | $flasharrays = @('','','') 23 | $pureuser = '' 24 | $pureuserpwd = '' 25 | $logfolder = 'C:\folder\folder\etc\' 26 | $unmaplogfile = 'unmap.txt' 27 | $powercliversion = 6 #only change if your PowerCLI version is earlier than 6.0 28 | #End of parameters 29 | <# 30 | *******Disclaimer:****************************************************** 31 | This scripts are offered "as is" with no warranty. While this 32 | scripts is tested and working in my environment, it is recommended that you test 33 | this script in a test lab before using in a production environment. Everyone can 34 | use the scripts/commands provided here without any written permission but I 35 | will not be liable for any damage or loss to the system. 36 | ************************************************************************ 37 | 38 | This script will identify Pure Storage FlashArray volumes and issue UNMAP against them. The script uses the best practice 39 | recommendation block count of 1% of the free capacity of the datastore. All operations are logged to a file and also 40 | output to the screen. REST API calls to the array before and after UNMAP will report on how much (if any) space has been reclaimed. 41 | 42 | This can be run directly from PowerCLI or from a standard PowerShell prompt. PowerCLI must be installed on the local host regardless. 43 | 44 | Supports: 45 | -PowerShell 3.0 or later 46 | -Pure Storage PowerShell SDK 1.0 or later 47 | -PowerCLI 6.0 Release 1 or later (5.5/5.8 is likely fine, but not tested with this script version) 48 | -REST API 1.4 and later 49 | -Purity 4.1 and later 50 | -FlashArray 400 Series and //m 51 | -vCenter 5.5 and later 52 | -Each FlashArray datastore must be present to at least one ESXi version 5.5 or later host or it will not be reclaimed 53 | #> 54 | #Create log folder if non-existent 55 | If (!(Test-Path -Path $logfolder)) { New-Item -ItemType Directory -Path $logfolder } 56 | $logfile = $logfolder + (Get-Date -Format o |ForEach-Object {$_ -Replace ':', '.'}) + $unmaplogfile 57 | 58 | add-content $logfile ' __________________________' 59 | add-content $logfile ' /++++++++++++++++++++++++++\' 60 | add-content $logfile ' /++++++++++++++++++++++++++++\' 61 | add-content $logfile ' /++++++++++++++++++++++++++++++\' 62 | add-content $logfile ' /++++++++++++++++++++++++++++++++\' 63 | add-content $logfile ' /++++++++++++++++++++++++++++++++++\' 64 | add-content $logfile ' /++++++++++++/----------\++++++++++++\' 65 | add-content $logfile ' /++++++++++++/ \++++++++++++\' 66 | add-content $logfile ' /++++++++++++/ \++++++++++++\' 67 | add-content $logfile ' /++++++++++++/ \++++++++++++\' 68 | add-content $logfile ' /++++++++++++/ \++++++++++++\' 69 | add-content $logfile ' \++++++++++++\ /++++++++++++/' 70 | add-content $logfile ' \++++++++++++\ /++++++++++++/' 71 | add-content $logfile ' \++++++++++++\ /++++++++++++/' 72 | add-content $logfile ' \++++++++++++\ /++++++++++++/' 73 | add-content $logfile ' \++++++++++++\ /++++++++++++/' 74 | add-content $logfile ' \++++++++++++\' 75 | add-content $logfile ' \++++++++++++\' 76 | add-content $logfile ' \++++++++++++\' 77 | add-content $logfile ' \++++++++++++\' 78 | add-content $logfile ' \------------\' 79 | add-content $logfile 'Pure Storage VMware ESXi UNMAP Script v3.1' 80 | add-content $logfile '----------------------------------------------------------------------------------------------------' 81 | 82 | #Connect to FlashArray via REST 83 | $facount=0 84 | $purevolumes=@() 85 | $purevol=$null 86 | $EndPoint= @() 87 | $Pwd = ConvertTo-SecureString $pureuserpwd -AsPlainText -Force 88 | $Creds = New-Object System.Management.Automation.PSCredential ($pureuser, $pwd) 89 | 90 | <#Connect to FlashArray via REST with the SDK 91 | Creates an array of connections for as many FlashArrays as you have entered into the $flasharrays variable. 92 | Assumes the same credentials are in use for every FlashArray 93 | #> 94 | 95 | foreach ($flasharray in $flasharrays) 96 | { 97 | if ($facount -eq 0) 98 | { 99 | $EndPoint = @(New-PfaArray -EndPoint $flasharray -Credentials $Creds -IgnoreCertificateError) 100 | $purevolumes += Get-PfaVolumes -Array $EndPoint[$facount] 101 | $tempvols = @(Get-PfaVolumes -Array $EndPoint[$facount]) 102 | $arraysnlist = @($tempvols.serial[0].substring(0,16)) 103 | } 104 | else 105 | { 106 | $EndPoint += New-PfaArray -EndPoint $flasharray -Credentials $Creds -IgnoreCertificateError 107 | $purevolumes += Get-PfaVolumes -Array $EndPoint[$facount] 108 | $tempvols = Get-PfaVolumes -Array $EndPoint[$facount] 109 | $arraysnlist += $tempvols.serial[0].substring(0,16) 110 | } 111 | $facount = $facount + 1 112 | } 113 | 114 | add-content $logfile 'Connected to the following FlashArray(s):' 115 | add-content $logfile $flasharrays 116 | add-content $logfile '----------------------------------------------------------------------------------------------------' 117 | 118 | #Important PowerCLI if not done and connect to vCenter. Adds PowerCLI Snapin if 5.8 and earlier. For PowerCLI no import is needed since it is a module 119 | $snapin = Get-PSSnapin -Name vmware.vimautomation.core -ErrorAction SilentlyContinue 120 | if ($snapin.Name -eq $null ) 121 | { 122 | if ($powercliversion -ne 6) {Add-PsSnapin VMware.VimAutomation.Core} 123 | } 124 | Set-PowerCLIConfiguration -invalidcertificateaction 'ignore' -confirm:$false |out-null 125 | Set-PowerCLIConfiguration -Scope Session -WebOperationTimeoutSeconds -1 -confirm:$false |out-null 126 | connect-viserver -Server $vcenter -username $vcuser -password $vcpass|out-null 127 | add-content $logfile ('Connected to vCenter at ' + $vcenter) 128 | add-content $logfile '----------------------------------------------------------------------------------------------------' 129 | 130 | #Gather VMFS Datastores and identify how many are Pure Storage volumes 131 | $datastores = $cluster | get-datastore 132 | add-content $logfile 'Found the following datastores:' 133 | add-content $logfile $datastores 134 | add-content $logfile '----------------------------------------------------------------------------------------------------' 135 | 136 | #Starting UNMAP Process on datastores 137 | $volcount=0 138 | $purevol = $null 139 | foreach ($datastore in $datastores) 140 | { 141 | $esx = $datastore | get-vmhost | where-object {($_.version -like '5.5.*') -or ($_.version -like '6.0.*')} |Select-Object -last 1 142 | if ($datastore.Type -ne 'VMFS') 143 | { 144 | add-content $logfile ('This volume is not a VMFS volume it is of type ' + $datastore.Type + ' and cannot be reclaimed. Skipping...') 145 | add-content $logfile '----------------------------------------------------------------------------------------------------' 146 | } 147 | else 148 | { 149 | $lun = $datastore.ExtensionData.Info.Vmfs.Extent.DiskName | select-object -last 1 150 | $datastore.ExtensionData.Info.Vmfs.Extent.DiskName 151 | $esxcli=get-esxcli -VMHost $esx 152 | add-content $logfile ('The datastore named ' + $datastore + ' is being examined') 153 | add-content $logfile '' 154 | add-content $logfile ('The ESXi named ' + $esx + ' will run the UNMAP/reclaim operation') 155 | add-content $logfile '' 156 | if ($lun -like 'naa.624a9370*') 157 | { 158 | $volserial = ($lun.ToUpper()).substring(12) 159 | $purevol = $purevolumes | where-object { $_.serial -eq $volserial } 160 | for($i=0; $i -lt $arraysnlist.count; $i++) 161 | { 162 | if ($arraysnlist[$i] -eq ($volserial.substring(0,16))) 163 | { 164 | $arraychoice = $i 165 | } 166 | } 167 | $volinfo = Get-PfaVolumeSpaceMetrics -Array $EndPoint[$arraychoice] -VolumeName $purevol.name 168 | $volreduction = '{0:N3}' -f ($volinfo.data_reduction) 169 | $volphysicalcapacity = '{0:N3}' -f ($volinfo.volumes/1024/1024/1024) 170 | add-content $logfile 'This datastore is a Pure Storage Volume.' 171 | add-content $logfile $lun 172 | add-content $logfile '' 173 | add-content $logfile ('The current data reduction for this volume before UNMAP is ' + $volreduction + " to 1") 174 | add-content $logfile ('The physical space consumption in GB of this device after UNMAP is ' + $volphysicalcapacity) 175 | add-content $logfile '' 176 | #Calculating optimal block count. If VMFS is 75% full or more the count must be 200 MB only. Ideal block count is 1% of free space of the VMFS in MB 177 | if ((1 - $datastore.FreeSpaceMB/$datastore.CapacityMB) -ge .75) 178 | { 179 | $blockcount = 200 180 | add-content $logfile 'The volume is 75% or more full so the block count is overridden to 200 MB. This will slow down the reclaim dramatically' 181 | add-content $logfile 'It is recommended to either free up space on the volume or increase the capacity so it is less than 75% full' 182 | add-content $logfile ("The block count in MB will be " + $blockcount) 183 | } 184 | else 185 | { 186 | $blockcount = [math]::floor($datastore.FreeSpaceMB * .01) 187 | add-content $logfile ("The maximum allowed block count for this datastore is " + $blockcount) 188 | } 189 | $esxcli.storage.vmfs.unmap($blockcount, $datastore.Name, $null) |out-null 190 | Start-Sleep -s 10 191 | $volinfo = Get-PfaVolumeSpaceMetrics -Array $EndPoint[$arraychoice] -VolumeName $purevol.name 192 | $volreduction = '{0:N3}' -f ($volinfo.data_reduction) 193 | $volphysicalcapacitynew = '{0:N3}' -f ($volinfo.volumes/1024/1024/1024) 194 | $unmapsavings = ($volphysicalcapacity - $volphysicalcapacitynew) 195 | $volcount=$volcount+1 196 | add-content $logfile '' 197 | add-content $logfile ('The new data reduction for this volume after UNMAP is ' + $volreduction + " to 1") 198 | add-content $logfile ('The new physical space consumption in GB of this device after UNMAP is ' + $volphysicalcapacitynew) 199 | add-content $logfile ("$unmapsavings" + ' in GB has been reclaimed from the FlashArray from this volume') 200 | add-content $logfile '----------------------------------------------------------------------------------------------------' 201 | Start-Sleep -s 5 202 | } 203 | else 204 | { 205 | add-content $logfile 'This datastore is NOT a Pure Storage Volume. Skipping...' 206 | add-content $logfile $lun 207 | add-content $logfile '----------------------------------------------------------------------------------------------------' 208 | } 209 | } 210 | } 211 | #disconnecting sessions 212 | foreach ($flasharray in $endpoint) 213 | { 214 | Disconnect-PfaArray -Array $flasharray 215 | } -------------------------------------------------------------------------------- /unmapsdkunattended.ps1: -------------------------------------------------------------------------------- 1 | param( 2 | [Parameter(Position=0,mandatory=$true)] 3 | [String[]] $flasharrays, 4 | [Parameter(Position=1,mandatory=$true)] 5 | [String] $vcenter, 6 | [Parameter(Position=2,mandatory=$true)] 7 | [String] $logfolder, 8 | [String] $loginsightserver, 9 | [String] $loginsightagentID 10 | ) 11 | #Optional Log Insight information. Only needed if you want to send the results to a Log Insight instance 12 | if ($loginsightserver -and $loginsightagentID) 13 | { 14 | $useloginsight = "y" 15 | } 16 | 17 | #To configure credential files, use the script here: https://github.com/codyhosterman/powercli/blob/master/unattendedUnmapConfigurator.ps1 18 | 19 | <# 20 | *************************************************************************************************** 21 | VMWARE POWERCLI AND PURE STORAGE POWERSHELL SDK MUST BE INSTALLED ON THE MACHINE THIS IS RUNNING ON 22 | *************************************************************************************************** 23 | 24 | For info, refer to www.codyhosterman.com 25 | 26 | *******Disclaimer:****************************************************** 27 | This scripts are offered "as is" with no warranty. While this 28 | scripts is tested and working in my environment, it is recommended that you test 29 | this script in a test lab before using in a production environment. Everyone can 30 | use the scripts/commands provided here without any written permission but I 31 | will not be liable for any damage or loss to the system. 32 | ************************************************************************ 33 | 34 | This script will identify Pure Storage FlashArray volumes and issue UNMAP against them. The script uses the best practice 35 | recommendation block count of 1% of the free capacity of the datastore. All operations are logged to a file and also 36 | output to the screen. REST API calls to the array before and after UNMAP will report on how much (if any) space has been reclaimed. 37 | 38 | This can be run directly from PowerCLI or from a standard PowerShell prompt. PowerCLI must be installed on the local host regardless. 39 | 40 | Supports: 41 | -PowerShell 3.0 or later 42 | -Pure Storage PowerShell SDK 1.5 or later 43 | -PowerCLI 6.3 Release 1+ 44 | -REST API 1.4 and later 45 | -Purity 4.1 and later 46 | -FlashArray 400 Series and //m 47 | -vCenter 5.5 and later 48 | -Each FlashArray datastore must be present to at least one ESXi version 5.5 or later host or it will not be reclaimed 49 | #> 50 | 51 | #Create log folder if non-existent 52 | $logfile = join-path -path $logfolder -childpath ((Get-Date -Format o |ForEach-Object {$_ -Replace ':', '.'}) + "unmapresults.txt") 53 | 54 | #Configure optional Log Insight target 55 | if ($useloginsight -ieq "y") 56 | { 57 | add-content $logfile ('Results will be sent to the following Log Insight instance ' + $loginsightserver + ' with the UUID of ' + $loginsightagentID) 58 | } 59 | elseif ($useloginsight -ieq "n") 60 | { 61 | add-content $logfile ('Log Insight will not be used for external logging') 62 | } 63 | 64 | #Import PowerCLI. Requires PowerCLI version 6.3 or later. Will fail here if PowerCLI is not installed 65 | if ( !(Get-Module -Name VMware.VimAutomation.Core -ErrorAction SilentlyContinue) ) { 66 | if (Test-Path “C:\Program Files (x86)\VMware\Infrastructure\PowerCLI\Scripts\Initialize-PowerCLIEnvironment.ps1”) 67 | { 68 | . “C:\Program Files (x86)\VMware\Infrastructure\PowerCLI\Scripts\Initialize-PowerCLIEnvironment.ps1” |out-null 69 | } 70 | elseif (Test-Path “C:\Program Files (x86)\VMware\Infrastructure\vSphere PowerCLI\Scripts\Initialize-PowerCLIEnvironment.ps1”) 71 | { 72 | . “C:\Program Files (x86)\VMware\Infrastructure\vSphere PowerCLI\Scripts\Initialize-PowerCLIEnvironment.ps1” |out-null 73 | } 74 | if ( !(Get-Module -Name VMware.VimAutomation.Core -ErrorAction SilentlyContinue) ) 75 | { 76 | add-content $logfile ("PowerCLI not found. Please verify installation and retry.") 77 | add-content $logfile "Get it here: https://my.vmware.com/web/vmware/details?downloadGroup=PCLI650R1&productId=614" 78 | add-content $logfile "Terminating Script" 79 | return 80 | } 81 | } 82 | #Set 83 | Set-PowerCLIConfiguration -invalidcertificateaction "ignore" -confirm:$false |out-null 84 | Set-PowerCLIConfiguration -Scope Session -WebOperationTimeoutSeconds -1 -confirm:$false |out-null 85 | 86 | if ( !(Get-Module -ListAvailable -Name PureStoragePowerShellSDK -ErrorAction SilentlyContinue) ) { 87 | add-content $logfile ("FlashArray PowerShell SDK not found. Please verify installation and retry.") 88 | add-content $logfile "Get it here: https://github.com/PureStorage-Connect/PowerShellSDK" 89 | add-content $logfile "Terminating Script" 90 | return 91 | } 92 | add-content $logfile ' __________________________' 93 | add-content $logfile ' /++++++++++++++++++++++++++\' 94 | add-content $logfile ' /++++++++++++++++++++++++++++\' 95 | add-content $logfile ' /++++++++++++++++++++++++++++++\' 96 | add-content $logfile ' /++++++++++++++++++++++++++++++++\' 97 | add-content $logfile ' /++++++++++++++++++++++++++++++++++\' 98 | add-content $logfile ' /++++++++++++/----------\++++++++++++\' 99 | add-content $logfile ' /++++++++++++/ \++++++++++++\' 100 | add-content $logfile ' /++++++++++++/ \++++++++++++\' 101 | add-content $logfile ' /++++++++++++/ \++++++++++++\' 102 | add-content $logfile ' /++++++++++++/ \++++++++++++\' 103 | add-content $logfile ' \++++++++++++\ /++++++++++++/' 104 | add-content $logfile ' \++++++++++++\ /++++++++++++/' 105 | add-content $logfile ' \++++++++++++\ /++++++++++++/' 106 | add-content $logfile ' \++++++++++++\ /++++++++++++/' 107 | add-content $logfile ' \++++++++++++\ /++++++++++++/' 108 | add-content $logfile ' \++++++++++++\' 109 | add-content $logfile ' \++++++++++++\' 110 | add-content $logfile ' \++++++++++++\' 111 | add-content $logfile ' \++++++++++++\' 112 | add-content $logfile ' \------------\' 113 | add-content $logfile 'Pure Storage FlashArray VMware ESXi UNMAP Script v5.0' 114 | add-content $logfile '----------------------------------------------------------------------------------------------------' 115 | 116 | $faCredPath = join-path -Path $logfolder -childpath "faUnmapCreds.xml" 117 | $Creds = Import-Clixml -Path $faCredPath 118 | #Connect to FlashArray via REST 119 | 120 | $purevolumes=@() 121 | $purevol=$null 122 | $EndPoint= @() 123 | $arraysnlist = @() 124 | 125 | <# 126 | Connect to FlashArray via REST with the SDK 127 | Assumes the same credentials are in use for every FlashArray 128 | #> 129 | 130 | foreach ($flasharray in $flasharrays) 131 | { 132 | try 133 | { 134 | $tempArray = (New-PfaArray -EndPoint $flasharray -Credentials $Creds -IgnoreCertificateError -ErrorAction stop) 135 | $EndPoint += $temparray 136 | $purevolumes += Get-PfaVolumes -Array $tempArray 137 | $arraySN = Get-PfaArrayAttributes -Array $tempArray 138 | 139 | if ($arraySN.id[0] -eq "0") 140 | { 141 | $arraySN = $arraySN.id.Substring(1) 142 | $arraySN = $arraySN.substring(0,19) 143 | } 144 | else 145 | { 146 | $arraySN = $arraySN.id.substring(0,18) 147 | } 148 | $arraySN = $arraySN -replace '-','' 149 | $arraysnlist += $arraySN 150 | add-content $logfile "FlashArray shortened serial is $($arraySN)" 151 | } 152 | catch 153 | { 154 | add-content $logfile "" 155 | add-content $logfile ("Connection to FlashArray " + $flasharray + " failed. Please check credentials or IP/FQDN") 156 | add-content $logfile $Error[0] 157 | add-content $logfile "Terminating Script" 158 | return 159 | } 160 | } 161 | 162 | add-content $logfile '----------------------------------------------------------------------------------------------------' 163 | add-content $logfile 'Connected to the following FlashArray(s):' 164 | add-content $logfile $flasharrays 165 | add-content $logfile '----------------------------------------------------------------------------------------------------' 166 | 167 | $vcCredPath = join-path -Path $logfolder -childpath "vcUnmapCreds.xml" 168 | $Creds = Import-Clixml -Path $vcCredPath 169 | try 170 | { 171 | connect-viserver -Server $vcenter -Credential $Creds -ErrorAction Stop |out-null 172 | add-content $logfile ('Connected to the following vCenter:') 173 | add-content $logfile $vcenter 174 | add-content $logfile '----------------------------------------------------------------------------------------------------' 175 | } 176 | catch 177 | { 178 | add-content $logfile "Failed to connect to vCenter" 179 | add-content $logfile $vcenter 180 | add-content $logfile $Error[0] 181 | add-content $logfile "Terminating Script" 182 | return 183 | } 184 | 185 | #A function to make REST Calls to Log Insight 186 | function logInsightRestCall 187 | { 188 | $restvmfs = [ordered]@{ 189 | name = "Datastore" 190 | content = $datastore.Name 191 | } 192 | $restarray = [ordered]@{ 193 | name = "FlashArray" 194 | content = $endpoint[$arraychoice].endpoint 195 | } 196 | $restvol = [ordered]@{ 197 | name = "FlashArrayvol" 198 | content = $purevol.name 199 | } 200 | $restunmap = [ordered]@{ 201 | name = "ReclaimedSpaceGB" 202 | content = $reclaimedvirtualspace 203 | } 204 | $esxhost = [ordered]@{ 205 | name = "ESXihost" 206 | content = $esxchosen[$i].name 207 | } 208 | $devicenaa = [ordered]@{ 209 | name = "SCSINaa" 210 | content = $lun 211 | } 212 | $fields = @($restvmfs,$restarray,$restvol,$restunmap,$esxhost,$devicenaa) 213 | $restcall = @{ 214 | messages = ([Object[]]($messages = [ordered]@{ 215 | text = ("Completed an UNMAP operation on the VMFS volume named " + $datastore.Name + " that is on the FlashArray named " + $endpoint[$arraychoice].endpoint + ".") 216 | fields = ([Object[]]$fields) 217 | })) 218 | } |convertto-json -Depth 4 219 | $resturl = ("http://" + $loginsightserver + ":9000/api/v1/messages/ingest/" + $loginsightagentID) 220 | add-content $logfile "" 221 | if($i=0){add-content $logfile ("Posting results to Log Insight server: " + $loginsightserver)} 222 | try 223 | { 224 | $response = Invoke-RestMethod $resturl -Method Post -Body $restcall -ContentType 'application/json' -ErrorAction stop 225 | if($i=0){add-content $logfile "REST Call to Log Insight server successful"} 226 | } 227 | catch 228 | { 229 | add-content $logfile "REST Call failed to Log Insight server" 230 | add-content $logfile $error[0] 231 | add-content $logfile $resturl 232 | } 233 | } 234 | $arrayspacestart = @() 235 | foreach ($flasharray in $endpoint) 236 | { 237 | $arrayspacestart += Get-PfaArraySpaceMetrics -array $flasharray 238 | } 239 | #Gather VMFS Datastores and identify how many are Pure Storage volumes 240 | $reclaimeddatastores = @() 241 | $virtualspace = @() 242 | $physicalspace = @() 243 | $esxchosen = @() 244 | $expectedreturns = @() 245 | $datastores = get-datastore 246 | add-content $logfile 'Found the following datastores:' 247 | add-content $logfile $datastores 248 | add-content $logfile '----------------------------------------------------------------------------------------------------' 249 | 250 | #Starting UNMAP Process on datastores 251 | $purevol = $null 252 | foreach ($datastore in $datastores) 253 | { 254 | add-content $logfile (get-date) 255 | add-content $logfile ('The datastore named ' + $datastore + ' is being examined') 256 | $esx = $datastore | get-vmhost | where-object {($_.version -like '5.5.*') -or ($_.version -like '6.*')}| where-object {($_.ConnectionState -eq 'Connected')} |Select-Object -last 1 257 | $unmapconfig = "" 258 | if ($datastore.ExtensionData.Info.Vmfs.majorVersion -eq 6) 259 | { 260 | $esxcli=get-esxcli -VMHost $esx -v2 261 | add-content $logfile ("The datastore named " + $datastore.name + " is VMFS version 6. Checking Automatic UNMAP configuration...") 262 | $unmapargs = $esxcli.storage.vmfs.reclaim.config.get.createargs() 263 | $unmapargs.volumelabel = $datastore.name 264 | $unmapconfig = $esxcli.storage.vmfs.reclaim.config.get.invoke($unmapargs) 265 | } 266 | if ($datastore.Type -ne 'VMFS') 267 | { 268 | add-content $logfile ('This volume is not a VMFS volume, it is of type ' + $datastore.Type + ' and cannot be reclaimed. Skipping...') 269 | add-content $logfile '' 270 | add-content $logfile '----------------------------------------------------------------------------------------------------' 271 | } 272 | elseif ($esx.count -eq 0) 273 | { 274 | add-content $logfile ('This datastore has no 5.5 or later hosts to run UNMAP from. Skipping...') 275 | add-content $logfile '' 276 | add-content $logfile '----------------------------------------------------------------------------------------------------' 277 | 278 | } 279 | elseif ($unmapconfig.ReclaimPriority -eq "low") 280 | { 281 | add-content $logfile ('This VMFS has Automatic UNMAP enabled. No need to run a manual reclaim. Skipping...') 282 | add-content $logfile '' 283 | add-content $logfile '----------------------------------------------------------------------------------------------------' 284 | } 285 | else 286 | { 287 | $lun = $datastore.ExtensionData.Info.Vmfs.Extent.DiskName |select-object -unique 288 | if ($lun.count -eq 1) 289 | { 290 | add-content $logfile ("The UUID for this volume is " + $datastore.ExtensionData.Info.Vmfs.Extent.DiskName) 291 | $esxcli=get-esxcli -VMHost $esx -v2 292 | if ($lun -like 'naa.624a9370*') 293 | { 294 | $volserial = ($lun.ToUpper()).substring(12) 295 | $purevol = $purevolumes | where-object { $_.serial -eq $volserial } 296 | if ($purevol.name -eq $null) 297 | { 298 | add-content $logfile 'ERROR: This volume has not been found. Please make sure that all of the FlashArrays presented to this vCenter are entered into this script.' 299 | add-content $logfile '' 300 | add-content $logfile '----------------------------------------------------------------------------------------------------' 301 | continue 302 | 303 | } 304 | else 305 | { 306 | for($i=0; $i -lt $arraysnlist.count; $i++) 307 | { 308 | if ($arraysnlist[$i] -eq ($volserial.substring(0,16))) 309 | { 310 | $arraychoice = $i 311 | } 312 | } 313 | $arrayname = Get-PfaArrayAttributes -array $EndPoint[$arraychoice] 314 | add-content $logfile ('The volume is on the FlashArray ' + $arrayname.array_name) 315 | add-content $logfile ('This datastore is a Pure Storage volume named ' + $purevol.name) 316 | add-content $logfile '' 317 | add-content $logfile ('The ESXi named ' + $esx + ' will run the UNMAP/reclaim operation') 318 | add-content $logfile '' 319 | $volinfo = Get-PfaVolumeSpaceMetrics -Array $EndPoint[$arraychoice] -VolumeName $purevol.name 320 | $usedvolcap = ((1 - $volinfo.thin_provisioning)*$volinfo.size)/1024/1024/1024 321 | $virtualspace += '{0:N0}' -f ($usedvolcap) 322 | $physicalspace += '{0:N0}' -f ($volinfo.volumes/1024/1024/1024) 323 | $usedspace = $datastore.CapacityGB - $datastore.FreeSpaceGB 324 | $deadspace = '{0:N0}' -f ($usedvolcap - $usedspace) 325 | if ($deadspace -lt 0) 326 | { 327 | $deadspace = 0 328 | } 329 | add-content $logfile ('The current used space of this VMFS is ' + ('{0:N0}' -f ($usedspace)) + " GB") 330 | add-content $logfile ('The current used virtual space for its FlashArray volume is approximately ' + ('{0:N0}' -f ($usedvolcap)) + " GB") 331 | $reclaimable = ('{0:N0}' -f ($deadspace)) 332 | if ($reclaimable -like "-*") 333 | { 334 | $reclaimable = 0 335 | } 336 | $expectedreturns += $reclaimable 337 | add-content $logfile ('The minimum reclaimable virtual space for this FlashArray volume is ' + $reclaimable + ' GB') 338 | #Calculating optimal block count. If VMFS is 75% full or more the count must be 200 MB only. Ideal block count is 1% of free space of the VMFS in MB 339 | if ((1 - $datastore.FreeSpaceMB/$datastore.CapacityMB) -ge .75) 340 | { 341 | $blockcount = 200 342 | add-content $logfile 'The volume is 75% or more full so the block count is overridden to 200 MB. This will slow down the reclaim dramatically' 343 | add-content $logfile 'It is recommended to either free up space on the volume or increase the capacity so it is less than 75% full' 344 | add-content $logfile ("The block count in MB will be " + $blockcount) 345 | } 346 | else 347 | { 348 | $blockcount = [math]::floor($datastore.FreeSpaceMB * .008) 349 | add-content $logfile ("The maximum allowed block count for this datastore is " + $blockcount) 350 | } 351 | $unmapargs = $esxcli.storage.vmfs.unmap.createargs() 352 | $unmapargs.volumelabel = $datastore.Name 353 | $unmapargs.reclaimunit = $blockcount 354 | try 355 | { 356 | $reclaimeddatastores += $datastore 357 | $esxchosen += $esx 358 | add-content $logfile ("Running UNMAP on VMFS named " + $datastore.Name + "...") 359 | $esxcli.storage.vmfs.unmap.invoke($unmapargs) |out-null 360 | } 361 | catch 362 | { 363 | add-content $logfile "Failed to complete UNMAP to this volume. Most common cause is a PowerCLI timeout which means UNMAP will continue to completion in the background for this VMFS." 364 | add-content $logfile $Error[0] 365 | add-content $logfile "Moving to the next volume" 366 | continue 367 | } 368 | add-content $logfile '' 369 | add-content $logfile '----------------------------------------------------------------------------------------------------' 370 | } 371 | } 372 | else 373 | { 374 | add-content $logfile ('The volume is not a FlashArray device, skipping the UNMAP operation') 375 | add-content $logfile '' 376 | add-content $logfile '----------------------------------------------------------------------------------------------------' 377 | continue 378 | } 379 | } 380 | elseif ($lun.count -gt 1) 381 | { 382 | add-content $logfile ('The volume spans more than one SCSI device, skipping UNMAP operation') 383 | add-content $logfile '' 384 | add-content $logfile '----------------------------------------------------------------------------------------------------' 385 | continue 386 | } 387 | } 388 | } 389 | start-sleep 120 390 | $arrayspaceend = @() 391 | $arraychanges = @() 392 | $finaldatastores = @() 393 | $totalreclaimedvirtualspace = @() 394 | foreach ($fa in $endpoint) 395 | { 396 | $totalreclaimedvirtualspace += 0 397 | } 398 | for ($i=0;$i -lt $reclaimeddatastores.count;$i++) 399 | { 400 | $lun = $reclaimeddatastores[$i].ExtensionData.Info.Vmfs.Extent.DiskName |select-object -unique 401 | $volserial = ($lun.ToUpper()).substring(12) 402 | $purevol = $purevolumes | where-object { $_.serial -eq $volserial } 403 | for($a=0; $a -lt $arraysnlist.count; $a++) 404 | { 405 | if ($arraysnlist[$a] -eq ($volserial.substring(0,16))) 406 | { 407 | $arraychoice = $a 408 | } 409 | } 410 | $volinfo = Get-PfaVolumeSpaceMetrics -Array $EndPoint[$arraychoice] -VolumeName $purevol.name 411 | $usedvolcap = '{0:N0}' -f (((1 - $volinfo.thin_provisioning)*$volinfo.size)/1024/1024/1024) 412 | $newphysicalspace = '{0:N0}' -f ($volinfo.volumes/1024/1024/1024) 413 | $reclaimedvirtualspace = $virtualspace[$i] - $usedvolcap 414 | $reclaimedphysicalspace = $physicalspace[$i] - $newphysicalspace 415 | $totalreclaimedvirtualspace[$arraychoice] += $reclaimedvirtualspace 416 | if ($reclaimedvirtualspace -like "-*") 417 | { 418 | $reclaimedvirtualspace = 0 419 | } 420 | if ($reclaimedphysicalspace -like "-*") 421 | { 422 | $reclaimedphysicalspace = 0 423 | } 424 | $finaldatastores += New-Object psobject -Property @{Datastore=$($reclaimeddatastores[$i].name);Volume=$($purevol.name);ExpectedMinimumVirtualSpaceGBReclaimed=$($expectedreturns[$i]);ActualVirtualSpaceGBReclaimed=$($reclaimedvirtualspace);ActualPhysicalSpaceGBReclaimed=$($reclaimedphysicalspace)} 425 | if ($useloginsight -ieq "y"){logInsightRestCall} 426 | } 427 | 428 | for ($i=0;$i -lt $endpoint.count;$i++) 429 | { 430 | $arrayspaceend += Get-PfaArraySpaceMetrics -array $endpoint[$i] 431 | $physicalspacedifference = ($arrayspacestart[$i].volumes - $arrayspaceend[$i].volumes)/1024/1024/1024 432 | if ($physicalspacedifference -like "-*") 433 | { 434 | $physicalspacedifference = 0 435 | } 436 | if ($totalreclaimedvirtualspace[$i] -like "-*") 437 | { 438 | $virtualspacedifference = 0 439 | } 440 | else 441 | { 442 | $virtualspacedifference = $totalreclaimedvirtualspace[$i] 443 | } 444 | $arraychanges += New-Object psobject -Property @{FlashArray=$($arrayspaceend[$i].hostname);VirtualSpaceGBReclaimed=$('{0:N0}' -f ($virtualspacedifference));PhysicalSpaceGBReclaimed=$('{0:N0}' -f ($physicalspacedifference))} 445 | } 446 | add-content $logfile "FlashArray-level Reclamation Statistics:" 447 | $arraychanges|ft -autosize -Property FlashArray,VirtualSpaceGBReclaimed,PhysicalSpaceGBReclaimed | Out-File -FilePath $logfile -Append -Encoding ASCII 448 | 449 | add-content $logfile "Volume-level Reclamation Statistics:" 450 | $finaldatastores|ft -autosize -Property Datastore,Volume,ExpectedMinimumVirtualSpaceGBReclaimed,ActualVirtualSpaceGBReclaimed,ActualPhysicalSpaceGBReclaimed | Out-File -FilePath $logfile -Append -Encoding ASCII 451 | 452 | add-content $logfile ("Space reclaim operation for all FlashArray VMFS volumes is complete.") 453 | add-content $logfile "" 454 | #disconnecting sessions 455 | add-content $logfile ("Disconnecting vCenter and FlashArray sessions") 456 | disconnect-viserver -Server $vcenter -confirm:$false 457 | foreach ($flasharray in $endpoint) 458 | { 459 | Disconnect-PfaArray -Array $flasharray 460 | } --------------------------------------------------------------------------------