/
unmapsdk.ps1
500 lines (478 loc) · 23.6 KB
/
unmapsdk.ps1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
<#
***************************************************************************************************
VMWARE POWERCLI AND PURE STORAGE POWERSHELL SDK MUST BE INSTALLED ON THE MACHINE THIS IS RUNNING ON
***************************************************************************************************
For info, refer to www.codyhosterman.com
*******Disclaimer:******************************************************
This scripts are offered "as is" with no warranty. While this
scripts is tested and working in my environment, it is recommended that you test
this script in a test lab before using in a production environment. Everyone can
use the scripts/commands provided here without any written permission but I
will not be liable for any damage or loss to the system.
************************************************************************
This script will identify Pure Storage FlashArray volumes and issue UNMAP against them. The script uses the best practice
recommendation block count of 1% of the free capacity of the datastore. All operations are logged to a file and also
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.
This can be run directly from PowerCLI or from a standard PowerShell prompt. PowerCLI must be installed on the local host regardless.
Supports:
-PowerShell 3.0 or later
-Pure Storage PowerShell SDK 1.5 or later
-PowerCLI 6.3 Release 1+
-REST API 1.4 and later
-Purity 4.1 and later
-FlashArray 400 Series and //m
-vCenter 5.5 and later
-Each FlashArray datastore must be present to at least one ESXi version 5.5 or later host or it will not be reclaimed
#>
#Create log folder if non-existent
write-host "Please choose a directory to store the script log"
function ChooseFolder([string]$Message, [string]$InitialDirectory)
{
$app = New-Object -ComObject Shell.Application
$folder = $app.BrowseForFolder(0, $Message, 0, $InitialDirectory)
$selectedDirectory = $folder.Self.Path
return $selectedDirectory
}
$logfolder = ChooseFolder -Message "Please select a log file directory" -InitialDirectory 'MyComputer'
$logfile = $logfolder + '\' + (Get-Date -Format o |ForEach-Object {$_ -Replace ':', '.'}) + "unmapresults.txt"
write-host "Script result log can be found at $logfile" -ForegroundColor Green
#Configure optional Log Insight target
$useloginsight = read-host "Would you like to send the UNMAP results to a Log Insight instance (y/n)"
while (($useloginsight -ine "y") -and ($useloginsight -ine "n"))
{
write-host "Invalid entry, please enter y or n"
$useloginsight = read-host "Would you like to send the UNMAP results to a Log Insight instance (y/n)"
}
if ($useloginsight -ieq "y")
{
$loginsightserver = read-host "Enter in the FQDN or IP of Log Insight"
$loginsightagentID = read-host "Enter a Log Insight Agent ID"
add-content $logfile ('Results will be sent to the following Log Insight instance ' + $loginsightserver + ' with the UUID of ' + $loginsightagentID)
}
elseif ($useloginsight -ieq "n")
{
add-content $logfile ('Log Insight will not be used for external logging')
}
#Import PowerCLI. Requires PowerCLI version 6.3 or later. Will fail here if PowerCLI is not installed
if ( !(Get-Module -Name VMware.VimAutomation.Core -ErrorAction SilentlyContinue) ) {
if (Test-Path “C:\Program Files (x86)\VMware\Infrastructure\PowerCLI\Scripts\Initialize-PowerCLIEnvironment.ps1”)
{
. “C:\Program Files (x86)\VMware\Infrastructure\PowerCLI\Scripts\Initialize-PowerCLIEnvironment.ps1” |out-null
}
elseif (Test-Path “C:\Program Files (x86)\VMware\Infrastructure\vSphere PowerCLI\Scripts\Initialize-PowerCLIEnvironment.ps1”)
{
. “C:\Program Files (x86)\VMware\Infrastructure\vSphere PowerCLI\Scripts\Initialize-PowerCLIEnvironment.ps1” |out-null
}
if ( !(Get-Module -Name VMware.VimAutomation.Core -ErrorAction SilentlyContinue) )
{
write-host ("PowerCLI not found. Please verify installation and retry.") -BackgroundColor Red
write-host "Terminating Script" -BackgroundColor Red
add-content $logfile ("PowerCLI not found. Please verify installation and retry.")
add-content $logfile "Get it here: https://my.vmware.com/web/vmware/details?downloadGroup=PCLI650R1&productId=614"
add-content $logfile "Terminating Script"
return
}
}
#Set
Set-PowerCLIConfiguration -invalidcertificateaction "ignore" -confirm:$false |out-null
Set-PowerCLIConfiguration -Scope Session -WebOperationTimeoutSeconds -1 -confirm:$false |out-null
if ( !(Get-Module -ListAvailable -Name PureStoragePowerShellSDK -ErrorAction SilentlyContinue) ) {
write-host ("FlashArray PowerShell SDK not found. Please verify installation and retry.") -BackgroundColor Red
write-host "Get it here: https://github.com/PureStorage-Connect/PowerShellSDK"
write-host "Terminating Script" -BackgroundColor Red
add-content $logfile ("FlashArray PowerShell SDK not found. Please verify installation and retry.")
add-content $logfile "Get it here: https://github.com/PureStorage-Connect/PowerShellSDK"
add-content $logfile "Terminating Script"
return
}
write-host ' __________________________'
write-host ' /++++++++++++++++++++++++++\'
write-host ' /++++++++++++++++++++++++++++\'
write-host ' /++++++++++++++++++++++++++++++\'
write-host ' /++++++++++++++++++++++++++++++++\'
write-host ' /++++++++++++++++++++++++++++++++++\'
write-host ' /++++++++++++/----------\++++++++++++\'
write-host ' /++++++++++++/ \++++++++++++\'
write-host ' /++++++++++++/ \++++++++++++\'
write-host ' /++++++++++++/ \++++++++++++\'
write-host ' /++++++++++++/ \++++++++++++\'
write-host ' \++++++++++++\ /++++++++++++/'
write-host ' \++++++++++++\ /++++++++++++/'
write-host ' \++++++++++++\ /++++++++++++/'
write-host ' \++++++++++++\ /++++++++++++/'
write-host ' \++++++++++++\ /++++++++++++/'
write-host ' \++++++++++++\'
write-host ' \++++++++++++\'
write-host ' \++++++++++++\'
write-host ' \++++++++++++\'
write-host ' \------------\'
write-host 'Pure Storage FlashArray VMware ESXi UNMAP Script v5.0'
write-host '----------------------------------------------------------------------------------------------------'
$FAcount = 0
$inputOK = $false
do
{
try
{
[int]$FAcount = Read-Host "How many FlashArrays do you need to enter (enter a number)"
$inputOK = $true
}
catch
{
Write-Host -ForegroundColor red "INVALID INPUT! Please enter a numeric value."
}
}
until ($inputOK)
$flasharrays = @()
for ($i=0;$i -lt $FAcount;$i++)
{
$flasharray = read-host "Please enter a FlashArray IP or FQDN"
$flasharrays += $flasharray
}
$Creds = $Host.ui.PromptForCredential("FlashArray Credentials", "Please enter your FlashArray username and password.", "","")
#Connect to FlashArray via REST
$purevolumes=@()
$purevol=$null
$EndPoint= @()
$arraysnlist = @()
<#
Connect to FlashArray via REST with the SDK
Assumes the same credentials are in use for every FlashArray
#>
foreach ($flasharray in $flasharrays)
{
try
{
$tempArray = (New-PfaArray -EndPoint $flasharray -Credentials $Creds -IgnoreCertificateError -ErrorAction stop)
$EndPoint += $temparray
$purevolumes += Get-PfaVolumes -Array $tempArray
$arraySN = Get-PfaArrayAttributes -Array $tempArray
if ($arraySN.id[0] -eq "0")
{
$arraySN = $arraySN.id.Substring(1)
$arraySN = $arraySN.substring(0,19)
}
else
{
$arraySN = $arraySN.id.substring(0,18)
}
$arraySN = $arraySN -replace '-',''
$arraysnlist += $arraySN
add-content $logfile "FlashArray shortened serial is $($arraySN)"
}
catch
{
add-content $logfile ""
add-content $logfile ("Connection to FlashArray " + $flasharray + " failed. Please check credentials or IP/FQDN")
add-content $logfile $Error[0]
add-content $logfile "Terminating Script"
return
}
}
add-content $logfile '----------------------------------------------------------------------------------------------------'
add-content $logfile 'Connected to the following FlashArray(s):'
add-content $logfile $flasharrays
add-content $logfile '----------------------------------------------------------------------------------------------------'
write-host ""
$vcenter = read-host "Please enter a vCenter IP or FQDN"
$newcreds = Read-host "Re-use the FlashArray credentials for vCenter? (y/n)"
while (($newcreds -ine "y") -and ($newcreds -ine "n"))
{
write-host "Invalid entry, please enter y or n"
$newcreds = Read-host "Re-use the FlashArray credentials for vCenter? (y/n)"
}
if ($newcreds -ieq "n")
{
$Creds = $Host.ui.PromptForCredential("vCenter Credentials", "Please enter your vCenter username and password.", "","")
}
try
{
connect-viserver -Server $vcenter -Credential $Creds -ErrorAction Stop |out-null
add-content $logfile ('Connected to the following vCenter:')
add-content $logfile $vcenter
add-content $logfile '----------------------------------------------------------------------------------------------------'
}
catch
{
write-host "Failed to connect to vCenter" -BackgroundColor Red
write-host $vcenter
write-host $Error[0]
write-host "Terminating Script" -BackgroundColor Red
add-content $logfile "Failed to connect to vCenter"
add-content $logfile $vcenter
add-content $logfile $Error[0]
add-content $logfile "Terminating Script"
return
}
write-host ""
#A function to make REST Calls to Log Insight
function logInsightRestCall
{
$restvmfs = [ordered]@{
name = "Datastore"
content = $datastore.Name
}
$restarray = [ordered]@{
name = "FlashArray"
content = $endpoint[$arraychoice].endpoint
}
$restvol = [ordered]@{
name = "FlashArrayvol"
content = $purevol.name
}
$restunmap = [ordered]@{
name = "ReclaimedSpaceGB"
content = $reclaimedvirtualspace
}
$esxhost = [ordered]@{
name = "ESXihost"
content = $esxchosen[$i].name
}
$devicenaa = [ordered]@{
name = "SCSINaa"
content = $lun
}
$fields = @($restvmfs,$restarray,$restvol,$restunmap,$esxhost,$devicenaa)
$restcall = @{
messages = ([Object[]]($messages = [ordered]@{
text = ("Completed an UNMAP operation on the VMFS volume named " + $datastore.Name + " that is on the FlashArray named " + $endpoint[$arraychoice].endpoint + ".")
fields = ([Object[]]$fields)
}))
} |convertto-json -Depth 4
$resturl = ("http://" + $loginsightserver + ":9000/api/v1/messages/ingest/" + $loginsightagentID)
add-content $logfile ""
if($i=0){add-content $logfile ("Posting results to Log Insight server: " + $loginsightserver)}
try
{
$response = Invoke-RestMethod $resturl -Method Post -Body $restcall -ContentType 'application/json' -ErrorAction stop
if($i=0){add-content $logfile "REST Call to Log Insight server successful"}
}
catch
{
add-content $logfile "REST Call failed to Log Insight server"
add-content $logfile $error[0]
add-content $logfile $resturl
}
}
$arrayspacestart = @()
foreach ($flasharray in $endpoint)
{
$arrayspacestart += Get-PfaArraySpaceMetrics -array $flasharray
}
#Gather VMFS Datastores and identify how many are Pure Storage volumes
$reclaimeddatastores = @()
$virtualspace = @()
$physicalspace = @()
$esxchosen = @()
$expectedreturns = @()
$datastores = get-datastore
add-content $logfile 'Found the following datastores:'
add-content $logfile $datastores
add-content $logfile '----------------------------------------------------------------------------------------------------'
#Starting UNMAP Process on datastores
write-host "Please be patient--this process can take a long time"
$purevol = $null
foreach ($datastore in $datastores)
{
add-content $logfile (get-date)
add-content $logfile ('The datastore named ' + $datastore + ' is being examined')
$esx = $datastore | get-vmhost | where-object {($_.version -like '5.5.*') -or ($_.version -like '6.*')}| where-object {($_.ConnectionState -eq 'Connected')} |Select-Object -last 1
$unmapconfig = ""
if ($datastore.ExtensionData.Info.Vmfs.majorVersion -eq 6)
{
$esxcli=get-esxcli -VMHost $esx -v2
add-content $logfile ("The datastore named " + $datastore.name + " is VMFS version 6. Checking Automatic UNMAP configuration...")
$unmapargs = $esxcli.storage.vmfs.reclaim.config.get.createargs()
$unmapargs.volumelabel = $datastore.name
$unmapconfig = $esxcli.storage.vmfs.reclaim.config.get.invoke($unmapargs)
}
if ($datastore.Type -ne 'VMFS')
{
add-content $logfile ('This volume is not a VMFS volume, it is of type ' + $datastore.Type + ' and cannot be reclaimed. Skipping...')
add-content $logfile ''
add-content $logfile '----------------------------------------------------------------------------------------------------'
}
elseif ($esx.count -eq 0)
{
add-content $logfile ('This datastore has no 5.5 or later hosts to run UNMAP from. Skipping...')
add-content $logfile ''
add-content $logfile '----------------------------------------------------------------------------------------------------'
}
elseif ($unmapconfig.ReclaimPriority -eq "low")
{
add-content $logfile ('This VMFS has Automatic UNMAP enabled. No need to run a manual reclaim. Skipping...')
add-content $logfile ''
add-content $logfile '----------------------------------------------------------------------------------------------------'
}
else
{
$lun = $datastore.ExtensionData.Info.Vmfs.Extent.DiskName |select-object -unique
if ($lun.count -eq 1)
{
add-content $logfile ("The UUID for this volume is " + $datastore.ExtensionData.Info.Vmfs.Extent.DiskName)
$esxcli=get-esxcli -VMHost $esx -v2
if ($lun -like 'naa.624a9370*')
{
$volserial = ($lun.ToUpper()).substring(12)
$purevol = $purevolumes | where-object { $_.serial -eq $volserial }
if ($purevol.name -eq $null)
{
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.'
add-content $logfile ''
add-content $logfile '----------------------------------------------------------------------------------------------------'
continue
}
else
{
for($i=0; $i -lt $arraysnlist.count; $i++)
{
if ($arraysnlist[$i] -eq ($volserial.substring(0,16)))
{
$arraychoice = $i
}
}
$arrayname = Get-PfaArrayAttributes -array $EndPoint[$arraychoice]
add-content $logfile ('The volume is on the FlashArray ' + $arrayname.array_name)
add-content $logfile ('This datastore is a Pure Storage volume named ' + $purevol.name)
add-content $logfile ''
add-content $logfile ('The ESXi named ' + $esx + ' will run the UNMAP/reclaim operation')
add-content $logfile ''
$volinfo = Get-PfaVolumeSpaceMetrics -Array $EndPoint[$arraychoice] -VolumeName $purevol.name
$usedvolcap = ((1 - $volinfo.thin_provisioning)*$volinfo.size)/1024/1024/1024
$virtualspace += '{0:N0}' -f ($usedvolcap)
$physicalspace += '{0:N0}' -f ($volinfo.volumes/1024/1024/1024)
$usedspace = $datastore.CapacityGB - $datastore.FreeSpaceGB
$deadspace = '{0:N0}' -f ($usedvolcap - $usedspace)
if ($deadspace -lt 0)
{
$deadspace = 0
}
add-content $logfile ('The current used space of this VMFS is ' + ('{0:N0}' -f ($usedspace)) + " GB")
add-content $logfile ('The current used virtual space for its FlashArray volume is approximately ' + ('{0:N0}' -f ($usedvolcap)) + " GB")
$reclaimable = ('{0:N0}' -f ($deadspace))
if ($reclaimable -like "-*")
{
$reclaimable = 0
}
$expectedreturns += $reclaimable
add-content $logfile ('The minimum reclaimable virtual space for this FlashArray volume is ' + $reclaimable + ' GB')
#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
if ((1 - $datastore.FreeSpaceMB/$datastore.CapacityMB) -ge .75)
{
$blockcount = 200
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'
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'
add-content $logfile ("The block count in MB will be " + $blockcount)
}
else
{
$blockcount = [math]::floor($datastore.FreeSpaceMB * .008)
add-content $logfile ("The maximum allowed block count for this datastore is " + $blockcount)
}
$unmapargs = $esxcli.storage.vmfs.unmap.createargs()
$unmapargs.volumelabel = $datastore.Name
$unmapargs.reclaimunit = $blockcount
try
{
$reclaimeddatastores += $datastore
$esxchosen += $esx
write-host ("Running UNMAP on VMFS named " + $datastore.Name + "...")
$esxcli.storage.vmfs.unmap.invoke($unmapargs) |out-null
}
catch
{
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."
add-content $logfile $Error[0]
add-content $logfile "Moving to the next volume"
continue
}
add-content $logfile ''
add-content $logfile '----------------------------------------------------------------------------------------------------'
}
}
else
{
add-content $logfile ('The volume is not a FlashArray device, skipping the UNMAP operation')
add-content $logfile ''
add-content $logfile '----------------------------------------------------------------------------------------------------'
continue
}
}
elseif ($lun.count -gt 1)
{
add-content $logfile ('The volume spans more than one SCSI device, skipping UNMAP operation')
add-content $logfile ''
add-content $logfile '----------------------------------------------------------------------------------------------------'
continue
}
}
}
$arrayspaceend = @()
$arraychanges = @()
$finaldatastores = @()
$totalreclaimedvirtualspace = 0
start-sleep 120
for ($i=0;$i -lt $reclaimeddatastores.count;$i++)
{
$lun = $reclaimeddatastores[$i].ExtensionData.Info.Vmfs.Extent.DiskName |select-object -unique
$volserial = ($lun.ToUpper()).substring(12)
$purevol = $purevolumes | where-object { $_.serial -eq $volserial }
for($a=0; $a -lt $arraysnlist.count; $a++)
{
if ($arraysnlist[$a] -eq ($volserial.substring(0,16)))
{
$arraychoice = $a
}
}
$volinfo = Get-PfaVolumeSpaceMetrics -Array $EndPoint[$arraychoice] -VolumeName $purevol.name
$usedvolcap = '{0:N0}' -f (((1 - $volinfo.thin_provisioning)*$volinfo.size)/1024/1024/1024)
$newphysicalspace = '{0:N0}' -f ($volinfo.volumes/1024/1024/1024)
$reclaimedvirtualspace = $virtualspace[$i] - $usedvolcap
$reclaimedphysicalspace = $physicalspace[$i] - $newphysicalspace
$totalreclaimedvirtualspace += $reclaimedvirtualspace
if ($reclaimedvirtualspace -like "-*")
{
$reclaimedvirtualspace = 0
}
if ($reclaimedphysicalspace -like "-*")
{
$reclaimedphysicalspace = 0
}
$finaldatastores += New-Object psobject -Property @{Datastore=$($reclaimeddatastores[$i].name);Volume=$($purevol.name);ExpectedMinimumVirtualSpaceGBReclaimed=$($expectedreturns[$i]);ActualVirtualSpaceGBReclaimed=$($reclaimedvirtualspace);ActualPhysicalSpaceGBReclaimed=$($reclaimedphysicalspace)}
if ($useloginsight -ieq "y"){logInsightRestCall}
}
for ($i=0;$i -lt $endpoint.count;$i++)
{
$arrayspaceend += Get-PfaArraySpaceMetrics -array $endpoint[$i]
$physicalspacedifference = ($arrayspacestart[$i].volumes - $arrayspaceend[$i].volumes)/1024/1024/1024
if ($physicalspacedifference -like "-*")
{
$physicalspacedifference = 0
}
if ($totalreclaimedvirtualspace[$i] -like "-*")
{
$virtualspacedifference = 0
}
else
{
$virtualspacedifference = $totalreclaimedvirtualspace[$i]
}
$arraychanges += New-Object psobject -Property @{FlashArray=$($arrayspaceend[$i].hostname);VirtualSpaceGBReclaimed=$('{0:N0}' -f ($virtualspacedifference));PhysicalSpaceGBReclaimed=$('{0:N0}' -f ($physicalspacedifference))}
}
add-content $logfile "FlashArray-level Reclamation Statistics:"
write-host "FlashArray-level Reclamation Statistics:"
write-host ($arraychanges |ft -autosize -Property FlashArray,VirtualSpaceGBReclaimed,PhysicalSpaceGBReclaimed | Out-String )
$arraychanges|ft -autosize -Property FlashArray,VirtualSpaceGBReclaimed,PhysicalSpaceGBReclaimed | Out-File -FilePath $logfile -Append -Encoding ASCII
add-content $logfile "Volume-level Reclamation Statistics:"
write-host "Volume-level Reclamation Statistics:"
write-host ($finaldatastores |ft -autosize -Property Datastore,Volume,ExpectedMinimumVirtualSpaceGBReclaimed,ActualVirtualSpaceGBReclaimed,ActualPhysicalSpaceGBReclaimed | Out-String )
$finaldatastores|ft -autosize -Property Datastore,Volume,ExpectedMinimumVirtualSpaceGBReclaimed,ActualVirtualSpaceGBReclaimed,ActualPhysicalSpaceGBReclaimed | Out-File -FilePath $logfile -Append -Encoding ASCII
add-content $logfile ("Space reclaim operation for all FlashArray VMFS volumes is complete.")
add-content $logfile ""
#disconnecting sessions
add-content $logfile ("Disconnecting vCenter and FlashArray sessions")
disconnect-viserver -Server $vcenter -confirm:$false
foreach ($flasharray in $endpoint)
{
Disconnect-PfaArray -Array $flasharray
}