├── 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 | }
--------------------------------------------------------------------------------