├── Kickstart
├── BOOT.CFG
├── KS.CFG
├── Kickstart-VMHostIMM.ps1
└── vicredentials.xml
├── LICENSE
├── NSX
├── Power-NsxRole.ps1
└── README.md
├── README.md
├── Scripts
├── Copy-VMNotes2ComputerDescription.ps1
├── Find-VC.ps1
└── README.md
├── VAMI
├── README.md
├── VAMI.psd1
└── VAMI.psm1
├── VSAN
├── README.md
├── VSAN.psd1
└── VSAN.psm1
└── Vi-Module
├── Get-FunctionVersion.ps1
├── New-PercentageBar.ps1
├── README.md
├── Start-SleepProgress.ps1
├── Vi-Module.format.ps1xml
├── Vi-Module.psd1
├── Vi-Module.psm1
├── Vi-SDRS.ps1
└── Write-Menu.ps1
/Kickstart/BOOT.CFG:
--------------------------------------------------------------------------------
1 | bootstate=0
2 | title=Loading Lenovo ESXi silent installer
3 | kernel=/tboot.b00
4 | kernelopt=ks=cdrom:/KS.CFG
5 | modules=/b.b00 --- /jumpstrt.gz --- /useropts.gz --- /k.b00 --- /chardevs.b00 --- /a.b00 --- /user.b00 --- /sb.v00 --- /s.v00 --- /brcm.v00 --- /misc_cni.v00 --- /net_bnx2.v00 --- /net_bnx2.v01 --- /net_cnic.v00 --- /net_tg3.v00 --- /scsi_bnx.v00 --- /scsi_bnx.v01 --- /brcdprov.v00 --- /net_bna.v00 --- /scsi_bfa.v00 --- /elxnet.v00 --- /emulex_c.v00 --- /ima_be2i.v00 --- /lpfc.v00 --- /scsi_be2.v00 --- /ianet_ci.v00 --- /net_igb.v00 --- /net_ixgb.v00 --- /lsiprovi.v00 --- /scsi_meg.v00 --- /scsi_mpt.v00 --- /scsi_mpt.v01 --- /concrete.v00 --- /filetran.v00 --- /fupb.v00 --- /fwupdate.v00 --- /hwckvm.v00 --- /ilfu.v00 --- /immpasst.v00 --- /pciinfo.v00 --- /soibms.v00 --- /mlnxprov.v00 --- /net_mlx4.v00 --- /net_mlx4.v01 --- /ima_qla4.v00 --- /net_qlcn.v00 --- /net_qlge.v00 --- /qlnative.v00 --- /qlogic_c.v00 --- /scsi_qla.v00 --- /ata_pata.v00 --- /ata_pata.v01 --- /ata_pata.v02 --- /ata_pata.v03 --- /ata_pata.v04 --- /ata_pata.v05 --- /ata_pata.v06 --- /ata_pata.v07 --- /block_cc.v00 --- /ehci_ehc.v00 --- /weaselin.t00 --- /esx_dvfi.v00 --- /xlibs.v00 --- /ipmi_ipm.v00 --- /ipmi_ipm.v01 --- /ipmi_ipm.v02 --- /misc_dri.v00 --- /mtip32xx.v00 --- /net_be2n.v00 --- /net_e100.v00 --- /net_e100.v01 --- /net_enic.v00 --- /net_forc.v00 --- /net_nx_n.v00 --- /net_vmxn.v00 --- /ohci_usb.v00 --- /rste.v00 --- /sata_ahc.v00 --- /sata_ata.v00 --- /sata_sat.v00 --- /sata_sat.v01 --- /sata_sat.v02 --- /sata_sat.v03 --- /sata_sat.v04 --- /scsi_aac.v00 --- /scsi_adp.v00 --- /scsi_aic.v00 --- /scsi_fni.v00 --- /scsi_hps.v00 --- /scsi_ips.v00 --- /scsi_lpf.v00 --- /scsi_meg.v01 --- /scsi_meg.v02 --- /scsi_mpt.v02 --- /scsi_mpt.v03 --- /scsi_qla.v01 --- /uhci_usb.v00 --- /tools.t00 --- /lnvcusto.v00 --- /xorg.v00 --- /imgdb.tgz --- /imgpayld.tgz
6 | build=
7 | updated=0
8 |
--------------------------------------------------------------------------------
/Kickstart/KS.CFG:
--------------------------------------------------------------------------------
1 | accepteula
2 | install --firstdisk=localesx,usb --ignoressd --overwritevmfs
3 | rootpw --iscrypted $6$zVUeVt1o$QXe1FD0ap1V..SnhD5XZtbNA4RmqYz8SP7RAcFYXbP5t4w20
4 | serialnum --esx=YOURL-ICENS-ENUMB-ERFOR-VMWARE
5 | reboot
6 | network --addvmportgroup=1 --bootproto=static --ip=10.200.21.160 --gateway=10.200.21.254 --netmask=255.255.255.0 --hostname=esxprd00 --nameserver=10.11.12.13
7 |
8 | %firstboot --interpreter=busybox
9 |
10 | ###
11 | ### DNS and Routing
12 | ###
13 | vName="esxprd00"
14 | vDNS1="10.11.12.13"
15 | vDNS2="10.11.12.14"
16 | vDom="your.internal.domain.com"
17 | esxcli system hostname set --fqdn="${vName}.${vDom}"
18 | esxcli network ip dns server add --server=${vDNS1}
19 | esxcli network ip dns server add --server=${vDNS2}
20 | esxcli network ip dns search add --domain=${vDom}
21 |
22 | ###
23 | ### Enable & Start ESXi Shell (TSM) & SSH (TSM-SSH)
24 | ###
25 | vim-cmd hostsvc/enable_esx_shell
26 | vim-cmd hostsvc/start_esx_shell
27 | vim-cmd hostsvc/enable_ssh
28 | vim-cmd hostsvc/start_ssh
29 |
30 | ###
31 | ### Create local datastore (VMFS6) & mark it SSD
32 | ###
33 | vDS="LocalSSD-00"
34 | NAA="$(ls /vmfs/devices/disks/ |grep -E 'naa\.\w+$')"
35 | END_SECTOR=$(eval expr $(partedUtil getptbl "/vmfs/devices/disks/${NAA}" | tail -1 | awk '{print $1 " \\* " $2 " \\* " $3}') - 1)
36 | partedUtil setptbl "/vmfs/devices/disks/${NAA}" "gpt" "1 2048 ${END_SECTOR} AA31E02A400F11DB9590000C2911D1B8 0"
37 | vmkfstools -C vmfs6 -b 1m -S ${vDS} "/vmfs/devices/disks/${NAA}:1"
38 | esxcli storage nmp satp rule add -s VMW_SATP_LOCAL -d ${NAA} -o enable_ssd
39 | esxcli storage core claiming reclaim -d ${NAA}
40 |
41 | ###
42 | ### vSS configuration
43 | ###
44 | vSS="vSwitch0"
45 | vUPlink1="vmnic0"
46 | vUPlink2="vmnic1"
47 | PG_MGMT="Management Network"
48 | VMK0_IP=10.200.21.160
49 |
50 | ### Uplinks ###
51 | esxcli network vswitch standard uplink add --uplink-name=${vUPlink1} --vswitch-name=${vSS}
52 | esxcli network vswitch standard uplink add --uplink-name=${vUPlink2} --vswitch-name=${vSS}
53 |
54 | ### CDP ###
55 | esxcli network vswitch standard set --cdp-status=down --mtu=1500 --vswitch-name=${vSS}
56 |
57 | ### Default vSS Policies ###
58 | esxcli network vswitch standard policy failover set --active-uplinks=${vUPlink1},${vUPlink2} --failback yes --failure-detection=link --load-balancing=portid --notify-switches yes --vswitch-name=${vSS}
59 | esxcli network vswitch standard policy security set --allow-forged-transmits yes --allow-mac-change yes --allow-promiscuous no --vswitch-name=${vSS}
60 | esxcli network vswitch standard policy shaping set --enabled false --vswitch-name=${vSS}
61 |
62 | ### Default PG Policies ###
63 | esxcli network vswitch standard portgroup policy failover set --active-uplinks=${vUPlink1},${vUPlink2} --portgroup-name=${PG_MGMT}
64 |
65 | ### VMkernel ports ###
66 | esxcli network ip interface add --interface-name=vmk0 --mtu=1500 --portgroup-name=${PG_MGMT}
67 | esxcli network ip interface ipv4 set --interface-name=vmk0 --ipv4=${VMK0_IP} --netmask=255.255.255.0 --type=static
68 | esxcli network ip interface tag add -i vmk0 -t Management
69 |
70 | ### Disable IPv6 ###
71 | esxcli system module parameters set -m tcpip4 -p ipv6=0
72 |
73 | ###
74 | ### Mount NFS datastore
75 | ###
76 | esxcli storage nfs add --host "nfs1.${vDom}" --share /share1 --volume-name NFSDS1
77 |
78 | ###
79 | ### Time Configuration
80 | ###
81 | cat > /etc/ntp.conf << __NTP_CONFIG__
82 | restrict default kod nomodify notrap nopeer
83 | restrict 127.0.0.1
84 | server NTP1
85 | server NTP2
86 | driftfile /etc/ntp.drift
87 | __NTP_CONFIG__
88 | /sbin/chkconfig ntpd on
89 |
90 | ###
91 | ### ESXi Advanced Settings
92 | ###
93 |
94 | ### Suppress ESXi Shell Warning ###
95 | esxcli system settings advanced set -o /UserVars/SuppressShellWarning -i 1
96 |
97 | ### Set shared VMTools location ###
98 | esxcli system settings advanced set -o /UserVars/ProductLockerLocation -s /vmfs/volumes/NFSDS1/productLocker
99 |
100 | ### Syslog.global.logDir ###
101 | vVol="$(esxcli storage filesystem list |grep VMFS |awk '{print $1}')"
102 | esxcli system syslog config set --logdir=${vVol}/logdir
103 |
104 | ### Scratch location ###
105 | vScratchDir="scratch"
106 | mkdir /vmfs/volumes/${vDS}/${vScratchDir}
107 | vim-cmd hostsvc/advopt/update ScratchConfig.ConfiguredScratchLocation string /vmfs/volumes/${vDS}/${vScratchDir}
108 |
109 | ### Network Coredump location ###
110 | VCIP="10.200.21.1"
111 | esxcli system coredump network set -v vmk0 -i ${VCIP} -o 6500
112 | esxcli system coredump network set -e true
113 |
114 | ###
115 | ### Enter maintenance mode
116 | ###
117 | esxcli system maintenanceMode set -e true
118 |
119 | ###
120 | ### Copy %firstboot script logs to persistent datastore
121 | ###
122 | cp /var/log/hostd.log "/vmfs/volumes/${vDS}/1boot-hostd.log"
123 | cp /var/log/esxi_install.log "/vmfs/volumes/${vDS}/1boot-install.log"
124 |
125 | ###
126 | ### Needed for configuration changes that could not be performed in esxcli
127 | ###
128 | esxcli system shutdown reboot -d 60 -r "1boot"
129 |
--------------------------------------------------------------------------------
/Kickstart/Kickstart-VMHostIMM.ps1:
--------------------------------------------------------------------------------
1 | #requires -Version 3.0 -Modules 'DnsServer', 'IMM-Module', 'VMware.VimAutomation.Core'
2 |
3 | <#
4 | .SYNOPSIS
5 | Deploy New VMHost with Kickstart ESXi ISO.
6 | .DESCRIPTION
7 | Deploy New VMHost with Kickstart ESXi ISO
8 | on IBM/Lenovo server via IMM.
9 | .PARAMETER IMMIPv4
10 | IBM server's IMM card IPv4 address.
11 | .PARAMETER IMMHostname
12 | IBM server's IMM card Hostname.
13 | .PARAMETER VMHostBorn
14 | New VMHost Hostname.
15 | .PARAMETER KickstartISO
16 | ESXi Kickstart prepared ISO image.
17 | .PARAMETER MgmtIPv4
18 | Management VMKernel Port IPv4 address.
19 | .PARAMETER vMotionIPv4
20 | vMotion VMKernel Port IPv4 address.
21 | .PARAMETER Env
22 | Virtual Environment - intended to support multiple Kickstart configurations.
23 | .PARAMETER CheckBusyIP
24 | Try to determine is Management and vMotion IP addresses
25 | are busy on the network by sending echo request.
26 | .PARAMETER SaveBootOrder
27 | Save and restore original server's Boot Order after ESXi host deployment.
28 | .PARAMETER Cred
29 | Rewrite VI Store Credentials.
30 | .EXAMPLE
31 | PS C:\scripts> .\Kickstart-VMHostIMM.ps1 -IMMIP "10.1.99.120" -VMHostBorn esxdmz08 -ISO '\\cifs\share\ESXi-5.5.0-1331820-IBM-20131115.iso' -MgmtIPv4 "192.168.203.16" -Env DMZ -CheckBusyIP:$false -SaveBootOrder
32 | Deploy ESXi host in DMZ environment without vMotion and save original server's Boot Order.
33 | .EXAMPLE
34 | PS C:\scripts> .\Kickstart-VMHostIMM.ps1 -IMM immprd11 -ESXi esxprd11 -MgmtIP "10.200.21.111" -vMotionIP "10.200.22.111"
35 | Deploy ESXi host in the default environment, use default ISO image.
36 | .EXAMPLE
37 | PS C:\scripts> .\Kickstart-VMHostIMM.ps1 -IMMIPv4 '10.99.200.103' -VMHostBorn 'esxdmz03' -Env DMZ -MgmtIPv4 '10.200.21.211'
38 | .EXAMPLE
39 | PS C:\scripts> .\Kickstart-VMHostIMM.ps1 -Cred
40 | Rewrite existing credentials in VI Credential Store.
41 | .NOTES
42 | Author: Roman Gelman
43 | Version 1.0 :: 11-Aug-2015 :: [Release]
44 | Version 2.0 :: 06-Feb-2017 :: [Change]
45 | [1] The script is fully based on IMM-Module now.
46 | [2] Get-EsxCli -V2 supported.
47 | .LINK
48 | https://ps1code.com/2015/08/27/kickstart-esxi-ibm-lenovo-powershell
49 | #>
50 |
51 | #region Parameters
52 |
53 | [CmdletBinding(DefaultParameterSetName='DNS')]
54 |
55 | Param (
56 |
57 | [Parameter(Mandatory,HelpMessage="IMM IPv4 Address",ParameterSetName='IP')]
58 | [Alias("IMMIP")]
59 | [ipaddress]$IMMIPv4
60 | ,
61 | [Parameter(Mandatory,HelpMessage="IMM Hostname",ParameterSetName='DNS')]
62 | [ValidatePattern('^[A-Za-z\d_-]{1,15}$')]
63 | [Alias("IMM")]
64 | [string]$IMMHostname
65 | ,
66 | [Parameter(Mandatory,HelpMessage="Deployed VMHost Hostname",ParameterSetName='DNS')]
67 | [Parameter(Mandatory,HelpMessage="Deployed VMHost Hostname",ParameterSetName='IP')]
68 | [ValidateNotNullorEmpty()]
69 | [Alias("ESXi")]
70 | [string]$VMHostBorn
71 | ,
72 | [Parameter(Mandatory,HelpMessage="Kickstart prepared ESXi installation ISO file",ParameterSetName='IP')]
73 | [Parameter(Mandatory,HelpMessage="Kickstart prepared ESXi installation ISO file",ParameterSetName='DNS')]
74 | [ValidatePattern('\.iso$')]
75 | [ValidateScript({Test-Path -Path $_ -PathType Leaf})]
76 | [Alias("ISO")]
77 | [string]$KickstartISO
78 | ,
79 | [Parameter(Mandatory,HelpMessage="Management VMKernel Port IPv4 address",ParameterSetName='IP')]
80 | [Parameter(Mandatory,HelpMessage="Management VMKernel Port IPv4 address",ParameterSetName='DNS')]
81 | [Alias ("MgmtIP")]
82 | [ipaddress]$MgmtIPv4
83 | ,
84 | [Parameter(Mandatory=$false,HelpMessage="vMotion VMKernel Port IPv4 address",ParameterSetName='IP')]
85 | [Parameter(Mandatory=$false,HelpMessage="vMotion VMKernel Port IPv4 address",ParameterSetName='DNS')]
86 | [Alias ("vMotionIP")]
87 | [ipaddress]$vMotionIPv4
88 | ,
89 | [Parameter(Mandatory=$false,HelpMessage="Virtual Environment",ParameterSetName='IP')]
90 | [Parameter(Mandatory=$false,HelpMessage="Virtual Environment",ParameterSetName='DNS')]
91 | [ValidateSet('PRD', 'DMZ')]
92 | [Alias ("Environment")]
93 | [string]$Env = 'PRD'
94 | ,
95 | [Parameter(Mandatory=$false,HelpMessage="Try to ping Management and vMotion IP addresses",ParameterSetName='IP')]
96 | [Parameter(Mandatory=$false,HelpMessage="Try to ping Management and vMotion IP addresses",ParameterSetName='DNS')]
97 | [Alias ("CheckIP")]
98 | [boolean]$CheckBusyIP = $true
99 | ,
100 | [Parameter(Mandatory=$false,HelpMessage="Save and restore original server's Boot Order",ParameterSetName='IP')]
101 | [Parameter(Mandatory=$false,HelpMessage="Save and restore original server's Boot Order",ParameterSetName='DNS')]
102 | [switch]$SaveBootOrder = $false
103 | ,
104 | [Parameter(Mandatory=$false,HelpMessage="Rewrite VI Store Credentials",ParameterSetName='VIStoreCred')]
105 | [switch]$Cred
106 |
107 | )
108 |
109 | #endregion Parameters
110 |
111 | #region Initialize Kickstart Configuration sets
112 |
113 | $Host.UI.RawUI.WindowTitle = "Kickstart ESXi - [$VMHostBorn]"
114 |
115 | Switch -exact ($Env) {
116 |
117 | 'PRD'
118 | {
119 | $ksCfgVMHost = 'esxprd00'
120 | $ksCfgRoot = 'root'
121 | $ksCfgMgmtIP = '10.200.21.160'
122 | $ksCfgMgmtMask = '255.255.255.0'
123 | $ksCfgvMoIP = '10.200.22.160'
124 | $ksCfgvMoMask = '255.255.255.0'
125 | $envDNSServer = 'DNS21'
126 | $envDNSZone = 'prod.contoso.local'
127 | $envBehindFW = $false
128 | Break
129 | }
130 | 'DMZ'
131 | {
132 | $ksCfgVMHost = 'esxdmz00'
133 | $ksCfgRoot = 'root'
134 | $ksCfgMgmtIP = '192.168.203.15'
135 | $ksCfgMgmtMask = '255.255.255.240'
136 | $ksCfgvMoIP = ''
137 | $envDNSServer = 'DMZDNS34'
138 | $envDNSZone = 'dmz.contoso.local'
139 | $envBehindFW = $true
140 | Break
141 | }
142 | }
143 |
144 | $IMM = If ($PSCmdlet.ParameterSetName -eq 'DNS') {$IMMHostname} Else {$IMMIPv4.IPAddressToString}
145 | $MgmtIPv4 = $MgmtIPv4.IPAddressToString
146 | $vMotionIPv4 = $vMotionIPv4.IPAddressToString
147 |
148 | #endregion Initialize Kickstart Configuration sets
149 |
150 | #region Prerequisites
151 |
152 | ###
153 | ### Rewrite VI credentials store for New deployed VMHost
154 | ###
155 |
156 | If ($PSCmdlet.ParameterSetName -eq 'VIStoreCred') {
157 |
158 | Write-Host "`nRewriting VI Credential Store item [$ksCfgVMHost-$ksCfgRoot] ..." -ForegroundColor Yellow
159 |
160 | $xxCred = $viCred = $null
161 | $xxUser = $xxPwd = ''
162 |
163 | Do {$xxCred = Get-Credential -UserName $ksCfgRoot -Message "New VMHost $ksCfgRoot"} While (!$xxCred)
164 |
165 | If ($xxCred.Password.Length -lt 1) {
166 | Write-Host "Zero length root password is not allowed`n" -ForegroundColor Red; Exit 1
167 | } Else {
168 | $xxPwd = $xxCred.GetNetworkCredential().Password
169 | $viCred = New-VICredentialStoreItem -Host $ksCfgVMHost -User $ksCfgRoot -Password $xxPwd
170 | If ($viCred) {Write-Host "VI Credentials Store item for [$ksCfgVMHost] VMHost successfully created`n" -ForegroundColor Green}
171 | Else {Write-Host "Failed to create VI Credentials Store item for [$ksCfgVMHost] VMHost`n" -ForegroundColor Red; Exit 1}
172 | }
173 |
174 | Exit 0
175 | }
176 |
177 | ###
178 | ### DNS server check
179 | ###
180 |
181 | If (!$envBehindFW) {
182 | $dnsCheck = Get-DnsServerZone -Name $envDNSZone -ComputerName $envDNSServer -ErrorAction SilentlyContinue
183 | If (!$dnsCheck) {Write-Host "Failed validate zone [$envDNSZone] on DNS server [$envDNSServer]" -ForegroundColor Red; Exit 1}
184 | }
185 |
186 | ###
187 | ### Check VI credentials store for New deployed VMHost credentials
188 | ###
189 |
190 | Write-Host "`nChecking VI Credential Store for [$ksCfgVMHost-$ksCfgRoot] credentials pair ..." -ForegroundColor Yellow
191 | $viCred = $null
192 | $viCred = Get-VICredentialStoreItem -Host $ksCfgVMHost -User $ksCfgRoot -ErrorAction SilentlyContinue
193 | If (!$viCred) {
194 | Write-Host "[$ksCfgVMHost-$ksCfgRoot] credentials pair not found, please supply" -ForegroundColor Yellow
195 |
196 | $xxCred = $null
197 | $xxUser = $xxPwd = ''
198 |
199 | Do {$xxCred = Get-Credential -UserName $ksCfgRoot -Message "New VMHost $ksCfgRoot"} While (!$xxCred)
200 |
201 | If ($xxCred.Password.Length -lt 1) {
202 | Write-Host "Zero length root password is not allowed`n" -ForegroundColor Red; Exit 1
203 | } Else {
204 | $xxPwd = $xxCred.GetNetworkCredential().Password
205 | $viCred = New-VICredentialStoreItem -Host $ksCfgVMHost -User $ksCfgRoot -Password $xxPwd
206 | If ($viCred) {Write-Host "VI Credentials Store item for [$ksCfgVMHost] VMHost successfully created" -ForegroundColor Green}
207 | Else {Write-Host "Failed to create VI Credentials Store item for [$ksCfgVMHost] VMHost" -ForegroundColor Red; Exit 1}
208 | }
209 | } Else {
210 | Write-Host "Credential pair for Host [$ksCfgVMHost] :: User [$ksCfgRoot] already exists" -ForegroundColor Green
211 | Write-Host "To overwrite it, run this script with [-Cred] parameter" -ForegroundColor Green
212 | }
213 |
214 | ###
215 | ### Check reply from initial IPv4 address of deployed VMHost (except environments that located behind firewalls)
216 | ###
217 |
218 | If (!$envBehindFW) {
219 |
220 | If (Test-Connection -ComputerName $ksCfgMgmtIP -Count 1 -Quiet) {
221 | Write-Host "Initial IP address [$ksCfgMgmtIP] is busy on the network, deployment canceled`n" -ForegroundColor Red
222 | Exit 1
223 | }
224 | }
225 |
226 | ###
227 | ### Check reply from Mgmt IPv4 address of deployed VMHost
228 | ###
229 |
230 | If ($CheckBusyIP -and (!$envBehindFW)) {
231 |
232 | Write-Host "`nChecking Management IP ..." -ForegroundColor Yellow
233 |
234 | If (Test-Connection -ComputerName $MgmtIPv4 -Count 1 -Quiet) {
235 | Write-Host "IP address [$MgmtIPv4] is busy on the network, deployment canceled`n" -ForegroundColor Red
236 | Exit 1
237 | } Else {
238 | Write-Host "IP address [$MgmtIPv4] is not in use on the network" -ForegroundColor Green
239 | }
240 | }
241 |
242 | ###
243 | ### Check reply from vMotion IPv4 address of deployed VMHost
244 | ###
245 |
246 | If ($vMotionIPv4 -and $CheckBusyIP -and (!$envBehindFW)) {
247 |
248 | Write-Host "`nChecking vMotion IP ..." -ForegroundColor Yellow
249 |
250 | If (Test-Connection -ComputerName $vMotionIPv4 -Count 1 -Quiet) {
251 | Write-Host "IP address [$vMotionIPv4] is busy on the network, deployment canceled`n" -ForegroundColor Red
252 | Exit 1
253 | } Else {
254 | Write-Host "IP address [$vMotionIPv4] is not in use on the network" -ForegroundColor Green
255 | }
256 | }
257 |
258 | ###
259 | ### Check DNS A-record, Exit only if A-record exists but not have the same IP address
260 | ###
261 |
262 | If (!$envBehindFW) {
263 | Write-Host "`nChecking [$VMHostBorn] DNS A-record ..." -ForegroundColor Yellow
264 | $dnsRecordA = $null
265 | $dnsRecordA = Get-DnsServerResourceRecord -ComputerName $envDNSServer `
266 | -ZoneName $envDNSZone -Name $VMHostBorn -RRType "A" -ErrorAction SilentlyContinue
267 | If ($dnsRecordA) {
268 | If ($MgmtIPv4 -eq $dnsRecordA.RecordData.IPv4Address.IPAddressToString) {
269 | Write-Host "DNS A-record exists and matches to the IP address [$MgmtIPv4]" -ForegroundColor Green
270 | } Else {
271 | Write-Host "DNS A-record exists but not matches: VMHost:[$MgmtIPv4] | DNS:[$($dnsRecordA.RecordData.IPv4Address.IPAddressToString)]" -ForegroundColor Red
272 | Exit 1
273 | }
274 | } Else {
275 | Write-Host "DNS A-record for [$VMHostBorn] doesn't exist" -ForegroundColor Green
276 | }
277 | }
278 |
279 | ###
280 | ### Get IMM credentials
281 | ###
282 |
283 | $IMMLoginID = Get-IMMSupervisorCred -ClearText UserName
284 | $IMMPwd = Get-IMMSupervisorCred
285 |
286 | $IMMBinding = " --host $IMM --user $IMMLoginID --password $IMMPwd"
287 | $rdBinding = " -s $IMM -l $IMMLoginID -p $IMMPwd"
288 |
289 | ###
290 | ### Save original Boot Order ###
291 | ###
292 |
293 | If ($SaveBootOrder) {
294 | Write-Host "`nSaving original Boot Order ..." -ForegroundColor Yellow
295 | $OrigBO = Get-IMMServerBootOrder -IMM $IMM -IMMLogin $IMMLoginID -IMMPwd $IMMPwd
296 | Write-Host "Original Boot Order is: [$($OrigBO.Boot1)->$($OrigBO.Boot2)->$($OrigBO.Boot3)->$($OrigBO.Boot4)]" -ForegroundColor Green
297 | }
298 |
299 | ###
300 | ### Change Boot Order: 'CD/DVD Rom' first
301 | ###
302 |
303 | Write-Host "`nChanging Boot Order ..." -ForegroundColor Yellow
304 | $DefaultBO = Set-IMMServerBootOrder -IMM $IMM -IMMLogin $IMMLoginID -IMMPwd $IMMPwd -Confirm:$false
305 | Write-Host "Boot Order changed: [$($DefaultBO.Boot1)]->[$($DefaultBO.Boot2)]->[$($DefaultBO.Boot3)]->[$($DefaultBO.Boot4)]" -ForegroundColor Green
306 |
307 | #endregion Prerequisites
308 |
309 | #region Mount Kickstart ISO image with IBM Remote Disk CLI
310 |
311 | ###
312 | ### Unmount any virtual media from IMM if exists
313 | ###
314 |
315 | Write-Host "`nUnmounting IMM Virtual Media drive ..." -ForegroundColor Yellow
316 | If (Unmount-IMMISO -IMM $IMM) {Write-Host "Successfully unmounted" -ForegroundColor Green} Else {Write-Host "Nothing to unmount" -ForegroundColor Green}
317 |
318 | ###
319 | ### Mount ISO
320 | ###
321 |
322 | Write-Host "`nMounting Kickstart ISO to IMM Virtual Media drive ..." -ForegroundColor Yellow
323 | $immMount = Mount-IMMISO -IMM $IMM -IMMLogin $IMMLoginID -IMMPwd $IMMPwd -ISO $KickstartISO
324 |
325 | If ($immMount.ISO) {
326 | Write-Host "Kickstart ISO file successfully mounted to IMM" -ForegroundColor Green
327 | } Else {
328 | Write-Host "Failed to mount Kickstart ISO" -ForegroundColor Red
329 | Exit 1
330 | }
331 |
332 | #endregion Mount Kickstart ISO image with IBM Remote Disk CLI
333 |
334 | #region PowerOn/Reboot server via IMM
335 |
336 | Write-Host "`nBooting IBM/LENOVO server ..." -ForegroundColor Yellow
337 |
338 | $InitialPowerState = (Get-IMMServerPowerState -IMM $IMM -IMMLogin $IMMLoginID -IMMPwd $IMMPwd).PowerState
339 |
340 | $PowerState = Switch ($InitialPowerState) {
341 | 'PoweredOff' {(Start-IMMServer -IMM $IMM -IMMLogin $IMMLoginID -IMMPwd $IMMPwd).PowerState; Break}
342 | 'PoweredOn' {(Reboot-IMMServerOS -IMM $IMM -IMMLogin $IMMLoginID -IMMPwd $IMMPwd -Confirm:$false).PowerState; Break}
343 | Default {Write-Host "Unable to determine the server PowerState" -ForegroundColor Red; Exit 1}
344 | }
345 |
346 | Switch ($PowerState) {
347 | 'Rebooted' {Write-Host "Server rebooted successfully" -ForegroundColor Green; Break}
348 | 'PoweredOn' {Write-Host "Server started successfully" -ForegroundColor Green; Break}
349 | Default {Write-Host "Failed to boot the server" -ForegroundColor Red; Exit 1}
350 | }
351 |
352 | #endregion PowerOn/Reboot server via IMM
353 |
354 | #region Waiting for New VMHost to boot 1-st time
355 |
356 | Write-Host "`nWaiting for New VMHost to boot first time [~30 min] ..." -ForegroundColor Yellow
357 | $pingCmdLine = "ping -n 1 $ksCfgMgmtIP"
358 | $i = 0
359 | Do
360 | {
361 | $i += 1
362 |
363 | #region Revert Original Boot Order
364 |
365 | If ($SaveBootOrder -and $i -eq 15) {
366 |
367 | Write-Host "`nReverting to Original Boot Order ..." -ForegroundColor Yellow
368 | $RevertBO = Set-IMMServerBootOrder -IMM $IMM -IMMLogin $IMMLoginID -IMMPwd $IMMPwd -Boot1 $OrigBO.Boot1 -Boot2 $OrigBO.Boot2 -Boot3 $OrigBO.Boot3 -Boot4 $OrigBO.Boot4
369 | If ($RevertBO.Boot1 -eq $OrigBO.Boot1) {
370 | Write-Host "Reverted successfully to: [$($OrigBO.Boot1)->$($OrigBO.Boot2)->$($OrigBO.Boot3)->$($OrigBO.Boot4)]`n" -ForegroundColor Green
371 | } Else {
372 | Write-Host "Sorry! Failed to revert to the Original Boot Order, please reconfigure manually`n" -ForegroundColor Red
373 | }
374 | }
375 |
376 | #endregion Revert Original Boot Order
377 |
378 | $pingOUT = ''; $pingOUT = Invoke-Expression -Command $pingCmdLine
379 | If ($pingOUT -like '*Reply from*') {
380 | Write-Host "First phase of VMHost deployment finished, server booted" -ForegroundColor Green
381 | } Else {
382 | Try
383 | {
384 | Write-Progress -Activity "VMHost Deployment [$VMHostBorn]" -Status "[$i] Deployment is in progress ..." `
385 | -CurrentOperation "Boot Phase N$([char]186)1..3" -PercentComplete ($i / 30 * 100 -as [int]) -ErrorAction Stop
386 | } Catch {Write-Progress -Activity "Completed" -Completed}
387 | }
388 | Start-Sleep -Seconds 60
389 | } While ($pingOUT -like '*Request timed out*')
390 |
391 | #endregion Waiting for New VMHost to boot 1-st time
392 |
393 | #region Waiting for New VMHost to reboot 2-nd time
394 |
395 | Write-Host "`nWaiting for New VMHost to reboot second time ..." -ForegroundColor Yellow
396 | $pingCmdLine = "ping -n 1 $ksCfgMgmtIP"
397 | $i = 0
398 | Do
399 | {
400 | $i += 1
401 | $pingOUT = ''; $pingOUT = Invoke-Expression -Command $pingCmdLine
402 | If ($pingOUT -like '*Request timed out*') {
403 | Write-Host "VMHost deployment successfully finished, server rebooted" -ForegroundColor Green
404 | } Else {
405 | Try
406 | {
407 | Write-Progress -Activity "VMHost Deployment [$VMHostBorn]" -Status "[$i] Deployment is still in progress ..." `
408 | -CurrentOperation "Boot Phase N$([char]186)2..3" -ErrorAction Stop
409 | } Catch {Write-Progress -Activity "Completed" -Completed}
410 | }
411 | Start-Sleep -Seconds 60
412 | } While ($pingOUT -like '*Reply from*')
413 |
414 | #endregion Waiting for New VMHost to reboot 2-nd time
415 |
416 | #region Waiting for New VMHost to boot after deployment
417 |
418 | Write-Host "`nWaiting for New VMHost to boot after deployment [~10 min] ..." -ForegroundColor Yellow
419 | $pingCmdLine = "ping -n 1 $ksCfgMgmtIP"
420 | $i = 0
421 | Do
422 | {
423 | $i += 1
424 | $pingOUT = ''; $pingOUT = Invoke-Expression -Command $pingCmdLine
425 | If ($pingOUT -like '*Reply from*') {
426 | Write-Host "VMHost deployment finished, server booted" -ForegroundColor Green
427 | } Else {
428 | Try
429 | {
430 | Write-Progress -Activity "VMHost Deployment [$VMHostBorn]" -Status "[$i .. ~10] Server boot is in progress ..." `
431 | -CurrentOperation "Boot Phase N$([char]186)3..3" -PercentComplete ($i / 10 * 100 -as [int]) -ErrorAction Stop
432 | } Catch {Write-Progress -Activity "Completed" -Completed}
433 | }
434 | Start-Sleep -Seconds 60
435 | } While ($pingOUT -like '*Request timed out*')
436 |
437 | #endregion Waiting for New VMHost to boot after deployment
438 |
439 | #region Connect to New VMHost
440 |
441 | Write-Host "`nConnecting to New VMHost ..." -ForegroundColor Yellow
442 | $objVMHost = $null
443 | $i = 0
444 | Do
445 | {
446 | $i += 1
447 | $objVMHost = Connect-VIServer -Server $ksCfgVMHost -WarningAction SilentlyContinue -ErrorAction Stop
448 | If ($objVMHost) {
449 | Write-Host "Successfully connected to New VMHost" -ForegroundColor Green
450 | } Else {
451 | Try
452 | {
453 | Write-Progress -Activity "VMHost Deployment [$VMHostBorn]" -Status "[$i] Waiting for New VMHost to load all modules ..." `
454 | -CurrentOperation "Finishing deployment" -ErrorAction Stop
455 | } Catch {Write-Progress -Activity "Completed" -Completed}
456 | }
457 | Start-Sleep -Seconds 30
458 | } While (!$objVMHost)
459 |
460 | #endregion Connect to New VMHost
461 |
462 | #region Configure New VMHost
463 |
464 | ###
465 | ### Rename VMHost
466 | ###
467 |
468 | Write-Host "`nRenaming New VMHost to [$VMHostBorn] ..." -ForegroundColor Yellow
469 | $esxcli = $null
470 | $renamed = $false
471 | $esxcli = Get-EsxCli -VMHost $ksCfgVMHost -ErrorAction SilentlyContinue -V2
472 | If ($esxcli) {$renamed = $esxcli.system.hostname.set.Invoke(@{'domain'=$envDNSZone;'host'=$VMHostBorn})}
473 | If ($renamed) {
474 | Write-Host "New VMHost successfully renamed to [$VMHostBorn]" -ForegroundColor Green
475 | } Else {Write-Host "Failed to rename New VMHost, remained generic [$ksCfgVMHost] hostname" -ForegroundColor Red}
476 |
477 | ###
478 | ### Change vMotion IP
479 | ###
480 |
481 | If ($vMotionIPv4 -and $ksCfgvMoIP) {
482 | Write-Host "`nChanging vMotion IPv4 to [vMotionIPv4] ..." -ForegroundColor Yellow
483 | $vMoIP = $false
484 | If ($esxcli) {$vMoIP = $esxcli.network.ip.interface.ipv4.set.Invoke(@{'interfacename'='vmk2';'ipv4'=$vMotionIPv4;'netmask'=$ksCfgvMoMask;'type'='static'})}
485 | If ($vMoIP) {
486 | Write-Host "vMotion IP successfully changed to [$vMotionIPv4]" -ForegroundColor Green
487 | } Else {Write-Host "Failed to change vMotion IP, remained generic [$ksCfgvMoIP] IP" -ForegroundColor Red}
488 | }
489 |
490 | ###
491 | ### Change Mgmt IP (very last change !!!)
492 | ###
493 |
494 | Write-Host "`nChanging Mgmt IPv4 to [$MgmtIPv4] ..." -ForegroundColor Yellow
495 | $mgmtIP = $false
496 | If ($esxcli) {$mgmtIP = $esxcli.network.ip.interface.ipv4.set.Invoke(@{'interfacename'='vmk0';'ipv4'=$MgmtIPv4;'netmask'=$ksCfgMgmtMask;'type'='static'})}
497 | If ($mgmtIP) {
498 | Write-Host "Management IP successfully changed to [$MgmtIPv4]" -ForegroundColor Green
499 | } Else {Write-Host "Failed to change Management IP, remained generic [$ksCfgMgmtIP] IP" -ForegroundColor Red}
500 |
501 | #endregion Configure New VMHost
502 |
503 | #region Disconnect from New VMHost
504 |
505 | Write-Host "`nDisconnecting from VMHost ..." -ForegroundColor Yellow
506 | Disconnect-VIServer -Server "*" -Confirm:$false -Force:$true
507 | If ($global:DefaultVIServers.Length -ne 0) {Write-Host "Failed to disconnect from VMHost" -ForegroundColor Red}
508 | Else {Write-Host "Successfully closed connections" -ForegroundColor Green}
509 |
510 | #endregion Disconnect from New VMHost
511 |
512 | #region Register New VMHost in DNS
513 |
514 | If (!$dnsRecordA -and !$envBehindFW) {
515 | Write-Host "`nRegistering new A-record [$VMHostBorn.$envDNSZone]" -ForegroundColor Yellow
516 | Add-DnsServerResourceRecordA -ComputerName $envDNSServer -ZoneName $envDNSZone -ErrorAction SilentlyContinue `
517 | -Name $VMHostBorn -IPv4Address $MgmtIPv4 -Confirm:$false -AllowUpdateAny:$false -CreatePtr
518 |
519 | $dnsRecordA = Get-DnsServerResourceRecord -ComputerName $envDNSServer `
520 | -ZoneName $envDNSZone -Name $VMHostBorn -RRType "A"
521 | If ($dnsRecordA) {
522 | Write-Host "DNS A-record for [$VMHostBorn.$envDNSZone] successfully created`n" -ForegroundColor Green
523 | } Else {
524 | Write-Host "Failed to create DNS A-record for [$VMHostBorn.$envDNSZone->$MgmtIPv4]`n" -ForegroundColor Red
525 | }
526 | }
527 |
528 | #endregion Register New VMHost in DNS
529 |
--------------------------------------------------------------------------------
/Kickstart/vicredentials.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 2.0
4 |
5 | esxprdXX
6 | root
7 | AQAAANCMnd8BFdERjHoAwE/Cl+sBAAAAF6kfTCxVukmVGdUuo/7GogAAAAACAAAAAAAQZgAAAAEAACAAAADfQQVi6Es9RmSn0+xaUvnHCWLQQ9kvp20+6TxEvRg6PQAAAAAOgAAAAAIAACAAAADPEzZGjGZrBV/BiGkcW+9orxwG2AR/hBpZG49Y35O1CRAAAAAlDPIQA721aZkl54pc1ZDYQAAAALHqFR1pfghM/N8mFMnVkTzHNXatzTgl/SWpjko3SYkrZn/WeMZQXrFsZ2jxuf8OT4mG7ogPrLcxBwMiAxeYo+g=
8 |
9 |
10 | esxdevXX
11 | root
12 | AQAAANCMnd8BFdERjHoAwE/Cl+sBAAAAF6kfTCxVukmVGdUuo/7GogAAAAACAAAAAAAQZgAAAAEAACAAAAA85ZEZ3Nkc68348Oq38SY4N2oBJRTV7NEmT0PLusYQYwAAAAAOgAAAAAIAACAAAACmgsKviqGkjm8uBfTSP5LKEeEWitwgeNlQ1ODNDSeYCxAAAABjgNLXQylh+Z5FmP10ISozQAAAAFEm/ct3On9lAdYAsbOkaVhN/qS0bkpQsBcPmavBNct1xe9kKwejTaGUP+5ZSjyhDwrPeSntiZFOsRm9BkX9/bk=
13 |
14 |
15 | esxdmzXX
16 | root
17 | AQAAANCMnd8BFdERjHoAwE/Cl+sBAAAAF6kfTCxVukmVGdUuo/7GogAAAAACAAAAAAAQZgAAAAEAACAAAACz6jbiJ9WbIpWHOVp0fkiS9QBRUTGJnY/ZtngBJCXjiQAAAAAOgAAAAAIAACAAAAB3SrKjqpGRElqMJpyVQnZBPv3P1c/D2loLEp68ve4vBhAAAADUO0hJF7FeHXVatIqjLl71QAAAAAJrnbMU61fyCKpG1wHoivQCQwiWHR72icfluaWXXG8hXYfEJk8QO/M8HNo21+xT4wEi1g/zv4CkRVhI9pJXEJA=
18 |
19 |
20 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Roman Gelman
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/NSX/Power-NsxRole.ps1:
--------------------------------------------------------------------------------
1 | #requires -Version 4.0 -Modules 'PowerNSX'
2 |
3 | Function Get-NsxRoleDisplayName
4 | {
5 |
6 | <#
7 | .SYNOPSIS
8 | Convert NSX Manager Role name to display name and vice versa.
9 | .DESCRIPTION
10 | This small helper function converts NSX API security Role name to
11 | the vSphere client Role Display name and vice versa.
12 | .PARAMETER NsxRole
13 | Specifies NSX role name or display name.
14 | .PARAMETER Reverse
15 | If specified, the parameter -NsxRole interpreted as Display name.
16 | .EXAMPLE
17 | PS C:\> 'super_user' | Get-NsxRoleDisplayName
18 | .EXAMPLE
19 | PS C:\> 'Enterprise Administrator' | Get-NsxRoleDisplayName -Reverse
20 | .EXAMPLE
21 | PS C:\> Get-NsxRoleDisplayName 'NSX Administrator' -Reverse
22 | .EXAMPLE
23 | PS C:\> Get-NsxRoleDisplayName -NsxRole 'security_admin'
24 | .NOTES
25 | Author :: Roman Gelman @rgelman75
26 | Version 1.0 :: 02-Dec-2018 :: [Release] :: Publicly available
27 | Version 1.1 :: 03-Dec-2018 :: [Change] :: Two new roles are introduced in NSX 6.4.2 (Security & Network Engineer)
28 | .LINK
29 | https://ps1code.com/2019/01/28/nsx-manager-roles-powernsx
30 | #>
31 |
32 | Param (
33 | [Parameter(Mandatory, ValueFromPipeline)]
34 | [string]$NsxRole
35 | ,
36 | [switch]$Reverse
37 | )
38 |
39 | Begin
40 | {
41 | $NsxRoles = @{
42 | 'super_user' = 'System Administrator';
43 | 'vshield_admin' = 'NSX Administrator';
44 | 'enterprise_admin' = 'Enterprise Administrator';
45 | 'security_admin' = 'Security Administrator';
46 | 'auditor' = 'Auditor';
47 | 'security_engineer' = 'Security Engineer';
48 | 'network_engineer' = 'Network Engineer'
49 | }
50 | }
51 | Process
52 | {
53 | if ($Reverse) { ($NsxRoles.GetEnumerator() | ? { $_.Value -eq $NsxRole }).Name }
54 | else { if ($NsxRoles.ContainsKey($NsxRole)) { $NsxRoles.$NsxRole } else { "$NsxRole" } }
55 | }
56 |
57 | } #EndFunction Get-NsxRoleDisplayName
58 |
59 | Function Add-NsxEntityAccessScope
60 | {
61 |
62 | <#
63 | .SYNOPSIS
64 | Assign vCenter user or group NSX Manager scope aware role in a custom Access Scope.
65 | .DESCRIPTION
66 | This function adds user or group to a NSX Manager and assigns
67 | scope aware role in a custom Access Scope.
68 | .PARAMETER AccessScope
69 | Specifies NSX Edge(s), DLR(s) or Logical Switch(es).
70 | .PARAMETER User
71 | Specifies vCenter user's UPN (ex: user@vsphere.local or user@domain.com).
72 | .PARAMETER Group
73 | Specifies vCenter group name (ex: group@vsphere.local or group@domain.com).
74 | .PARAMETER Role
75 | Specifies scope aware NSX role.
76 | .EXAMPLE
77 | PS C:\> Get-NsxEdge esg_Lab1 | Add-NsxEntityAccessScope -User NSXAdmin1@vsphere.local -Role Auditor -Debug
78 | .EXAMPLE
79 | PS C:\> Get-NsxLogicalSwitch -TransportZone trz_Lab | Add-NsxEntityAccessScope NSXAdmin1@domain.com -Verbose -Confirm:$false
80 | .EXAMPLE
81 | PS C:\> Get-NsxLogicalRouter dlr_Lab1 | Add-NsxEntityAccessScope -Group NSXAdmins@vsphere.local
82 | .EXAMPLE
83 | PS C:\> $scope = @(Get-NsxEdge esg_Lab1)
84 | PS C:\> $scope += Get-NsxLogicalRouter dlr_Lab1
85 | PS C:\> $scope += Get-NsxTransportZone trz_Lab | Get-NsxLogicalSwitch
86 | PS C:\> Add-NsxEntityAccessScope NSXAdmin1@vsphere.local -AccessScope $scope
87 | Compose a scope from multiple objects of different types (Edge, DLR and Logical switches).
88 | .NOTES
89 | Author :: Roman Gelman @rgelman75
90 | Shell :: Tested on PowerShell 5.0 | PowerCLi 6.5 | PowerNSX 3.0.1081
91 | Platform :: Tested on vSphere 6.5 : VCSA 6.5 & NSX 6.4
92 | Dependency :: PowerNSX Module
93 | Version 1.0 :: 04-Dec-2018 :: [Release] :: Publicly available
94 | .LINK
95 | https://ps1code.com/2019/01/28/nsx-manager-roles-powernsx
96 | #>
97 |
98 | [CmdletBinding(ConfirmImpact = 'High', SupportsShouldProcess, DefaultParameterSetName = 'USR')]
99 | [Alias('New-NsxEntityAccessScope')]
100 | [OutputType([pscustomobject])]
101 | Param (
102 | [Parameter(Mandatory, ValueFromPipeline)]
103 | $AccessScope
104 | ,
105 | [Parameter(Mandatory, Position = 1, ParameterSetName = 'USR')]
106 | [ValidatePattern("^.+@.+$")]
107 | [string[]]$User
108 | ,
109 | [Parameter(Mandatory, ParameterSetName = 'GRP')]
110 | [ValidatePattern("^.+@.+$")]
111 | [string[]]$Group
112 | ,
113 | [Parameter()]
114 | [ValidateSet('Auditor', 'Security Administrator',
115 | 'Security Engineer', 'Network Engineer')]
116 | [string]$Role = 'Security Administrator'
117 | )
118 |
119 | Begin
120 | {
121 | $NsxRole = Get-NsxRoleDisplayName $Role -Reverse
122 | $StartXml = "`n$NsxRole"
123 | $Scope = @()
124 | $EndXml = "`n"
125 |
126 | $Entity = @{}
127 | if ($PSCmdlet.ParameterSetName -eq 'USR')
128 | {
129 | $Entity.Add('Name', $User)
130 | $Entity.Add('Type', 'user')
131 | $Entity.Add('IsGroup', 'false')
132 | }
133 | else
134 | {
135 | $Entity.Add('Name', $Group)
136 | $Entity.Add('Type', 'group')
137 | $Entity.Add('IsGroup', 'true')
138 | }
139 |
140 | $EdgeName = @()
141 | }
142 | Process
143 | {
144 | $Scope += foreach ($NsxObject in $AccessScope)
145 | {
146 | if ($NsxObject -is [System.Xml.XmlElement])
147 | {
148 | if (($NsxObject | Get-Member -MemberType Property, NoteProperty).Name -contains 'objectTypeName')
149 | {
150 | $ObjId = 'objectid'
151 | $ObjType = ($NsxObject.objectTypeName).Replace('VirtualWire', 'Logical Switch')
152 | }
153 | else
154 | {
155 | $ObjId = 'id'
156 | $ObjType = 'NSX Edge'
157 | }
158 | "`n$($NsxObject.$ObjId)"
159 | $EdgeName += $NsxObject.name
160 | Write-Verbose "The $ObjType [$($NsxObject.$ObjId)] will be added to the scope"
161 | }
162 | else
163 | {
164 | Write-Verbose "The [$NsxObject] is not PowerNSX object"
165 | }
166 | }
167 | }
168 | End
169 | {
170 | $PostBody = $StartXml + $Scope + $EndXml
171 |
172 | foreach ($NsxServer in $DefaultNSXConnection)
173 | {
174 | if ((Invoke-NsxRestMethod -connection $NsxServer -method get -URI "/api/2.0/services/usermgmt/roles").list.string -contains $NsxRole)
175 | {
176 | foreach ($ACE in $Entity.Name)
177 | {
178 | Try
179 | {
180 | $XmlAce = (Invoke-NsxRestMethod -connection $NsxServer -method get -URI "/api/2.0/services/usermgmt/role/$ACE" -ea Stop).accessControlEntry
181 | $ResourceScope = if ($XmlAce.resource.name) { $XmlAce.resource.name -join ', ' } else { 'Global' }
182 | Write-Output "The $($Entity.Type) [$ACE] in NSX Manager [$($NsxServer.Server)] already has [$($XmlAce.role | Get-NsxRoleDisplayName)] role in the [$ResourceScope] scope"
183 | }
184 | Catch
185 | {
186 | if ($PSCmdlet.ShouldProcess("NSX Manager [$($NsxServer.Server)]", "Assign [$Role] role within Access Scope [$($EdgeName -join ', ')] to the $($Entity.Type) [$ACE]"))
187 | {
188 | $Uri = "/api/2.0/services/usermgmt/role/$($ACE)??isGroup=$($Entity.IsGroup)"
189 | Invoke-NsxRestMethod -connection $NsxServer -method post -URI $Uri -body $PostBody
190 | Write-Debug "`nUri: $Uri`nBody:`n$($PostBody | Format-XML)"
191 |
192 | [pscustomobject] @{
193 | NsxManager = $NsxServer.Server
194 | Entity = $ACE
195 | Type = (Get-Culture).TextInfo.ToTitleCase($Entity.Type)
196 | Role = $Role
197 | Scope = $($EdgeName -join ', ')
198 | }
199 | }
200 | }
201 | }
202 | }
203 | else
204 | {
205 | Write-Verbose "The NSX Manager [$($NsxServer.Server)] version $($NsxServer.Version) does not support [$Role] role"
206 | }
207 | }
208 | }
209 |
210 | } #EndFunction Add-NsxEntityAccessScope
211 |
212 | Function Remove-NsxEntityRoleAssignment
213 | {
214 |
215 | <#
216 | .SYNOPSIS
217 | Remove NSX Manager role assignment for any vCenter user or group.
218 | .DESCRIPTION
219 | This function removes NSX Manager role assignment for any vCenter user(s) or group(s).
220 | .PARAMETER User
221 | Specifies vCenter user's UPN (ex: user@vsphere.local or user@domain.com).
222 | .PARAMETER Group
223 | Specifies vCenter group name (ex: group@vsphere.local or group@domain.com).
224 | .EXAMPLE
225 | PS C:\> Remove-NsxEntityRoleAssignment -User NSXAdmin1@vsphere.local
226 | .EXAMPLE
227 | PS C:\> Remove-NsxEntityRoleAssignment -Group NSXAdmins@vsphere.local
228 | .EXAMPLE
229 | PS C:\> Remove-NsxEntityRoleAssignment NSXAdmin1@vsphere.local, NSXAdmin2@vsphere.local -Verbose
230 | .EXAMPLE
231 | PS C:\> Remove-NsxEntityRoleAssignment -Group NSXAdmins@vsphere.local, NSXAdmins@domain.com -Confirm:$false
232 | .NOTES
233 | Author :: Roman Gelman @rgelman75
234 | Shell :: Tested on PowerShell 5.0 | PowerCLi 6.5 | PowerNSX 3.0.1081
235 | Platform :: Tested on vSphere 6.5 : VCSA 6.5 & NSX 6.4
236 | Dependency :: PowerNSX Module
237 | Version 1.0 :: 03-Dec-2018 :: [Release] :: Publicly available
238 | .LINK
239 | https://ps1code.com/2019/01/28/nsx-manager-roles-powernsx
240 | #>
241 |
242 | [CmdletBinding(ConfirmImpact = 'High', SupportsShouldProcess, DefaultParameterSetName = 'USR')]
243 | [OutputType([pscustomobject])]
244 | Param (
245 | [Parameter(Mandatory, Position = 1, ParameterSetName = 'USR')]
246 | [ValidatePattern("^.+@.+$")]
247 | [string[]]$User
248 | ,
249 | [Parameter(Mandatory, ParameterSetName = 'GRP')]
250 | [ValidatePattern("^.+@.+$")]
251 | [string[]]$Group
252 | )
253 |
254 | Begin
255 | {
256 | $Entity = @{ }
257 | if ($PSCmdlet.ParameterSetName -eq 'USR')
258 | {
259 | $Entity.Add('Name', $User)
260 | $Entity.Add('Type', 'user')
261 | $Entity.Add('IsGroup', 'false')
262 | }
263 | else
264 | {
265 | $Entity.Add('Name', $Group)
266 | $Entity.Add('Type', 'group')
267 | $Entity.Add('IsGroup', 'true')
268 | }
269 | }
270 | Process
271 | {
272 | foreach ($NsxServer in $DefaultNSXConnection)
273 | {
274 | foreach ($ACE in $Entity.Name)
275 | {
276 | Try
277 | {
278 | $XmlAce = (Invoke-NsxRestMethod -connection $NsxServer -method get -URI "/api/2.0/services/usermgmt/role/$ACE").accessControlEntry
279 | $ResourceScope = if ($XmlAce.resource) { $XmlAce.resource.name -join ', ' } else { 'Global' }
280 | if ($PSCmdlet.ShouldProcess("NSX Manager [$($NsxServer.Server)] - scope [$ResourceScope]", "Remove [$($XmlAce.role | Get-NsxRoleDisplayName)] role assignment for the entity [$ACE]"))
281 | {
282 | Invoke-NsxRestMethod -connection $NsxServer -method 'del' -URI "/api/2.0/services/usermgmt/role/$($ACE)??isGroup=$($Entity.IsGroup)"
283 |
284 | [pscustomobject] @{
285 | NsxManager = $NsxServer.Server
286 | Entity = $ACE
287 | Type = (Get-Culture).TextInfo.ToTitleCase($Entity.Type)
288 | Role = $XmlAce.role | Get-NsxRoleDisplayName
289 | Scope = $ResourceScope
290 | }
291 | }
292 | }
293 | Catch
294 | {
295 | Write-Verbose "The $($Entity.Type) [$ACE] from NSX Manager [$($NsxServer.Server)] skipped because no role assigned"
296 | }
297 | }
298 | }
299 | }
300 | End { }
301 |
302 | } #EndFunction Remove-NsxEntityRoleAssignment
303 |
304 | Function Add-NsxEntityRoleAssignment
305 | {
306 |
307 | <#
308 | .SYNOPSIS
309 | Assign the NSX Manager role to any vCenter user or group.
310 | .DESCRIPTION
311 | This function assigns NSX Manager role to any vCenter user(s) or group(s).
312 | .PARAMETER User
313 | Specifies vCenter user's UPN (ex: user@vsphere.local or user@domain.com).
314 | .PARAMETER Group
315 | Specifies vCenter group name (ex: group@vsphere.local or group@domain.com).
316 | .PARAMETER Role
317 | Specifies NSX role.
318 | .EXAMPLE
319 | PS C:\> Add-NsxEntityRoleAssignment -User NSXAdmin1@vsphere.local
320 | .EXAMPLE
321 | PS C:\> Add-NsxEntityRoleAssignment -Group NSXAdmins@vsphere.local
322 | .EXAMPLE
323 | PS C:\> Add-NsxEntityRoleAssignment NSXAdmin1@vsphere.local, NSXAdmin2@vsphere.local -Verbose
324 | .EXAMPLE
325 | PS C:\> Add-NsxEntityRoleAssignment -Group NSXAdmins@vsphere.local, NSXAdmins@domain.com -Confirm:$false
326 | .NOTES
327 | Author :: Roman Gelman @rgelman75
328 | Shell :: Tested on PowerShell 5.0 | PowerCLi 6.5 | PowerNSX 3.0.1081
329 | Platform :: Tested on vSphere 6.5 : VCSA 6.5 & NSX 6.4
330 | Dependency :: PowerNSX Module
331 | Version 1.0 :: 03-Dec-2018 :: [Release] :: Publicly available
332 | .LINK
333 | https://ps1code.com/2019/01/28/nsx-manager-roles-powernsx
334 | #>
335 |
336 | [CmdletBinding(ConfirmImpact = 'High', SupportsShouldProcess, DefaultParameterSetName = 'USR')]
337 | [Alias('New-NsxEntityRoleAssignment')]
338 | [OutputType([pscustomobject])]
339 | Param (
340 | [Parameter(Mandatory, Position = 1, ParameterSetName = 'USR')]
341 | [ValidatePattern("^.+@.+$")]
342 | [string[]]$User
343 | ,
344 | [Parameter(Mandatory, ParameterSetName = 'GRP')]
345 | [ValidatePattern("^.+@.+$")]
346 | [string[]]$Group
347 | ,
348 | [Parameter()]
349 | [ValidateSet('Auditor', 'Security Administrator',
350 | 'Enterprise Administrator', 'NSX Administrator', 'System Administrator',
351 | 'Security Engineer', 'Network Engineer')]
352 | [string]$Role = 'Enterprise Administrator'
353 | )
354 |
355 | Begin
356 | {
357 | $NsxRole = Get-NsxRoleDisplayName $Role -Reverse
358 | $StartXml = ""
359 | $RoleXml = "`n$NsxRole"
360 | $RoleXml += if ('Auditor', 'Security Administrator', 'Security Engineer', 'Network Engineer' -contains $Role) { "`nglobalroot-0" }
361 | $EndXml = "`n"
362 |
363 | $Entity = @{ }
364 | if ($PSCmdlet.ParameterSetName -eq 'USR')
365 | {
366 | $Entity.Add('Name', $User)
367 | $Entity.Add('Type', 'user')
368 | $Entity.Add('IsGroup', 'false')
369 | }
370 | else
371 | {
372 | $Entity.Add('Name', $Group)
373 | $Entity.Add('Type', 'group')
374 | $Entity.Add('IsGroup', 'true')
375 | }
376 | }
377 | Process
378 | {
379 | foreach ($NsxServer in $DefaultNSXConnection)
380 | {
381 | if ((Invoke-NsxRestMethod -connection $NsxServer -method get -URI "/api/2.0/services/usermgmt/roles").list.string -contains $NsxRole)
382 | {
383 | foreach ($ACE in $Entity.Name)
384 | {
385 | Try
386 | {
387 | $XmlAce = (Invoke-NsxRestMethod -connection $NsxServer -method get -URI "/api/2.0/services/usermgmt/role/$ACE").accessControlEntry
388 | Write-Debug "`n$($XmlAce | Format-XML)"
389 | $ResourceScope = if ($XmlAce.resource) { $XmlAce.resource.name -join ', ' } else { 'Global' }
390 | Write-Output "The $($Entity.Type) [$($Entity.Name)] in NSX Manager [$($NsxServer.Server)] already has [$($XmlAce.role | Get-NsxRoleDisplayName)] role in the [$ResourceScope] scope"
391 | }
392 | Catch
393 | {
394 | if ($PSCmdlet.ShouldProcess("NSX Manager [$($NsxServer.Server)]", "Assign [$Role] role to the entity [$ACE]"))
395 | {
396 | $Uri = "/api/2.0/services/usermgmt/role/$($ACE)??isGroup=$($Entity.IsGroup)"
397 | $PostBody = $StartXml + $RoleXml + $EndXml
398 | Write-Debug "`nUri: $Uri`nBody:`n$($PostBody | Format-XML)"
399 | Invoke-NsxRestMethod -connection $NsxServer -method post -URI $Uri -body $PostBody
400 |
401 | [pscustomobject] @{
402 | NsxManager = $NsxServer.Server
403 | Entity = $ACE
404 | Type = (Get-Culture).TextInfo.ToTitleCase($Entity.Type)
405 | Role = $Role
406 | Scope = 'Global'
407 | }
408 | }
409 | }
410 | }
411 | }
412 | else
413 | {
414 | Write-Verbose "The NSX Manager [$($NsxServer.Server)] version $($NsxServer.Version) does not support [$Role] role"
415 | }
416 | }
417 | }
418 | End { }
419 |
420 | } #EndFunction Add-NsxEntityRoleAssignment
421 |
422 | Function Get-NsxEntityRoleAssignment
423 | {
424 |
425 | <#
426 | .SYNOPSIS
427 | Get users and groups who have been assigned a NSX Manager role.
428 | .DESCRIPTION
429 | This function retrieves information about local as well as vCenter
430 | users and groups who have been assigned a NSX Manager role.
431 | .PARAMETER Entity
432 | Specifies user or group pattern.
433 | .PARAMETER AccessScope
434 | If specified, only users and groups with Access Scope defined returned.
435 | .PARAMETER Role
436 | Specifies NSX role.
437 | .EXAMPLE
438 | PS C:\> Get-NsxEntityRoleAssignment | Format-Table -AutoSize
439 | .EXAMPLE
440 | PS C:\> Get-NsxEntityRoleAssignment -Entity '\admin'
441 | Search for user/group by name.
442 | .EXAMPLE
443 | PS C:\> Get-NsxEntityRoleAssignment nsx -Role 'Security Administrator'
444 | Search for user/group by name and role.
445 | .EXAMPLE
446 | PS C:\> Get-NsxEntityRoleAssignment -AccessScope
447 | Accounts with Access Scope defined.
448 | .EXAMPLE
449 | PS C:\> Get-NsxEntityRoleAssignment | Where-Object {$_.Domain -ne 'cli' -and !$_.Enabled}
450 | Make custom search for disabled domain accounts.
451 | .NOTES
452 | Author :: Roman Gelman @rgelman75
453 | Shell :: Tested on PowerShell 5.0 | PowerCLi 6.5 | PowerNSX 3.0.1081
454 | Platform :: Tested on vSphere 6.5 : VCSA 6.5 & NSX 6.4
455 | Dependency :: PowerNSX Module
456 | Version 1.0 :: 04-Dec-2018 :: [Release] :: Publicly available
457 | .LINK
458 | https://ps1code.com/2019/01/28/nsx-manager-roles-powernsx
459 | #>
460 |
461 | [CmdletBinding()]
462 | [OutputType([pscustomobject])]
463 | Param (
464 | [Parameter(Position = 0)]
465 | [ValidateNotNullOrEmpty()]
466 | [string]$Entity
467 | ,
468 | [Parameter()]
469 | [ValidateSet('Auditor', 'Security Administrator',
470 | 'Enterprise Administrator', 'NSX Administrator', 'System Administrator',
471 | 'Security Engineer', 'Network Engineer')]
472 | [string]$Role
473 | ,
474 | [Parameter()]
475 | [switch]$AccessScope
476 | )
477 |
478 | Begin { $GlobalScope = 'Global' }
479 | Process
480 | {
481 | foreach ($NsxServer in $DefaultNSXConnection)
482 | {
483 | $XmlAce = (Invoke-NsxRestMethod -connection $NsxServer -method get -URI "/api/2.0/services/usermgmt/users/vsm").users.userInfo
484 |
485 | $RoleAssigned += $XmlAce | % {
486 |
487 | $Type = if ($_.isGroup -eq 'false') { 'User' } else { 'Group' }
488 | $Enabled = if ($_.isEnabled -eq 'true') { $true } else { $false }
489 | $IsCli = if ($_.isCli -eq 'true') { $true } else { $false }
490 | $IsUni = if ($_.isUniversal -eq 'true') { $true } else { $false }
491 | $Scope = if ($_.hasGlobalObjectAccess -eq 'true') { $GlobalScope } else { ($_.accessControlEntry.resource.name | Sort-Object) -join ', ' }
492 | $Domain = if ($_.name -match '\\') { ([regex]::Match($_.name, '^(.+)\\')).Groups[1] } else { 'CLI' }
493 |
494 | [pscustomobject] @{
495 | NsxManager = $NsxServer.Server
496 | Domain = "$Domain".ToUpper()
497 | Entity = $_.name
498 | Type = $Type
499 | Enabled = $Enabled
500 | Universal = $IsUni
501 | Role = Get-NsxRoleDisplayName $_.accessControlEntry.role
502 | Scope = $Scope
503 | }
504 | }
505 | ### Filter by Entity name ###
506 | $RoleAssigned = if ($PSBoundParameters.ContainsKey('Entity')) { ($RoleAssigned).Where{ $_.Entity -imatch $([regex]::Escape($Entity)) } } else { $RoleAssigned }
507 | ### Filter by Role assigned ###
508 | $RoleAssigned = if ($PSBoundParameters.ContainsKey('Role')) { ($RoleAssigned).Where{ $_.Role -eq $Role } } else { $RoleAssigned }
509 | ### Return only roles with Access Scope defined ###
510 | $RoleAssigned = if ($AccessScope) { ($RoleAssigned).Where{ $_.Scope -ne $GlobalScope } } else { $RoleAssigned }
511 | }
512 | }
513 | End { return $RoleAssigned | Sort-Object 'Type', Entity }
514 |
515 | } #EndFunction Get-NsxEntityRoleAssignment
516 |
--------------------------------------------------------------------------------
/NSX/README.md:
--------------------------------------------------------------------------------
1 | # VMware NSX
2 |
3 | ## [Power-NsxRole.ps1](https://github.com/rgel/PowerCLi/blob/master/NSX/Power-NsxRole.ps1)
4 |
5 | ### Manage NSX Manager Roles leveraging [PowerNSX](https://github.com/vmware/powernsx) and NSX API
6 |
7 | Requirements: PowerShell 4 or above. To check, type the following command: `$PSVersionTable.PSVersion.Major`.
8 |
9 | To use this script, save the 'Power-NsxRole.ps1' file to your computer and go to the script directory, e.g. `cd C:\scripts`.
10 |
11 | Import the script to the current PowerShell session: `Import-Module .\Power-NsxRole.ps1 -Force`.
12 |
13 | Connect to your NSX Manager(s) by `Connect-NsxServer` cmdlet from the PowerNSX module.
14 |
15 | You are ready to invoke imported cmdlets. To see the cmdlets imported, type `Get-Command -Noun nsxentity*`.
16 |
17 | All the action cmdlets (`Add-`/`New-`/`Remove-`) are advanced functions and support `-Debug`, `-Verbose` and `-Confirm` parameters.
18 |
19 | For help on each individual cmdlet, run `Get-Help CmdletName -Full [-Online][-Examples]`.
20 |
21 | |No|Cmdlet|Description|
22 | |----|----|----|
23 | |1|[Get-NsxEntityRoleAssignment](https://ps1code.com/2019/01/28/nsx-manager-roles-powernsx)|Get users and groups who have been assigned a NSX Manager role|
24 | |2|[Add-NsxEntityRoleAssignment](https://ps1code.com/2019/01/28/nsx-manager-roles-powernsx)|Assign the NSX Manager role to any vCenter user or group|
25 | |3|[Add-NsxEntityAccessScope](https://ps1code.com/2019/01/28/nsx-manager-roles-powernsx)|Assign vCenter user or group NSX Manager scope aware role in a custom Access Scope. This replaces `Limit Scope` [capability](https://vswitchzero.com/2018/10/19/limiting-user-scope-and-permissions-in-nsx/), removed from 6.2 UI and later|
26 | |4|[Remove-NsxEntityRoleAssignment](https://ps1code.com/2019/01/28/nsx-manager-roles-powernsx)|Remove NSX Manager role assignment for any vCenter user or group|
27 | |5|[Get-NsxRoleDisplayName](https://ps1code.com/2019/01/28/nsx-manager-roles-powernsx)|Convert NSX Manager Role name to display name and vice versa (internal helper function)|
28 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # $${\color{blue}VMware \space PowerCLI \space Repo}$$
2 |
3 | ### $${\color{green}SCRIPTS}$$
4 |
5 | |No|Script|Description|
6 | |----|----|----|
7 | |1|[Copy-VMNotes2ComputerDescription.ps1](https://github.com/rgel/PowerCLi/blob/master/Scripts/Copy-VMNotes2ComputerDescription.ps1)|[Copy](https://ps1code.com/2015/12/14/copy-vmware-vm-notes-2-comp-descr) VMware `VM Notes` to Computer/AD Computer Account `Description`|
8 | |2|[Kickstart-VMHostIMM.ps1](https://github.com/rgel/PowerCLi/tree/master/Kickstart)|[Kickstart](https://ps1code.com/2015/08/27/kickstart-esxi-ibm-lenovo-powershell) ESXi hosts on IBM/LENOVO servers without PXE using PowerShell|
9 | |3|[Find-VC.ps1](https://github.com/rgel/PowerCLi/blob/master/Scripts/Find-VC.ps1)|Search VCenter VM throw direct connection to group of ESXi hosts|
10 | |4|[Power-NsxRole.ps1](https://github.com/rgel/PowerCLi/blob/master/NSX)|Manage `NSX Manager Roles` leveraging [PowerNSX](https://github.com/vmware/powernsx) and NSX API|
11 |
12 | ##
13 | ### $${\color{green}MODULES}$$
14 |
15 | |No|Module|Description|
16 | |----|----|----|
17 | |1|[Vi-Module](https://github.com/rgel/PowerCLi/tree/master/Vi-Module)|[VMware VI Automation](https://ps1code.com/category/vmware-powercli/vi-module)|
18 | |2|[VSAN](https://github.com/rgel/PowerCLi/tree/master/VSAN)|[VMware VSAN Automation](https://ps1code.com/category/vmware-powercli/vsan)
19 | |3|[VAMI](https://github.com/rgel/PowerCLi/tree/master/VAMI)|[Virtual Appliance Management Interface](https://ps1code.com/2017/05/11/vami-powercli-module)
20 |
--------------------------------------------------------------------------------
/Scripts/Copy-VMNotes2ComputerDescription.ps1:
--------------------------------------------------------------------------------
1 | #requires -Version 3.0
2 | #requires -Modules 'ActiveDirectory'
3 |
4 | <#
5 | .SYNOPSIS
6 | Copy VMware VM Notes to Computer/AD Computer Account description.
7 | .DESCRIPTION
8 | This script copy VM 'Notes' value to Computer description & Active Directory Computer Account description
9 | and add custom string and Cluster name to it. Example: "VM Notes | VMware :: Cluster"
10 | .PARAMETER Cluster
11 | Specifies target Cluster object(s) to get VM Notes from it.
12 | .PARAMETER CustomString
13 | Specifies custom string followed after the Notes.
14 | .PARAMETER HelperCsv
15 | Specifies intermediate export/import CSV file to preserve NON english chars.
16 | By default created in the script directory.
17 | .PARAMETER TargetEnv
18 | Specifies target environment (Local|Active Directory).
19 | .EXAMPLE
20 | PS C:\> cd C:\scriptpath
21 | PS C:\scriptpath> .\Copy-VMNotes2ComputerDescription.ps1 -Cluster (Get-Cluster prod)
22 | .EXAMPLE
23 | PS C:\scriptpath> Get-Cluster prod |.\Copy-VMNotes2ComputerDescription.ps1 AD -CustomString ' '
24 | .EXAMPLE
25 | PS C:\scriptpath> $res = Get-Cluster prod, test |.\Copy-VMNotes2ComputerDescription.ps1 -HelperCsv 'C:\reports\VMNotes.csv' -TargetEnv Local
26 | .EXAMPLE
27 | PS C:\scriptpath> Get-Cluster prod, test |.\Copy-VMNotes2ComputerDescription.ps1 -ErrorOnly |ft VM, ComputerName, Error -au
28 | Get failed computers only.
29 | .EXAMPLE
30 | PS C:\scriptpath> Get-Cluster test |.\Copy-VMNotes2ComputerDescription.ps1
31 | .EXAMPLE
32 | PS C:\scriptpath> Get-Cluster |.\Copy-VMNotes2ComputerDescription.ps1 -Verbose |epcsv -notype -Encoding utf8 .\Result.csv
33 | .NOTES
34 | Author :: Roman Gelman @rgelman75
35 | Requirement :: PowerShell 3.0
36 | Dependency :: ActiveDirectory PowerShell Module (part of R.S.A.T.)
37 | Version 1.0 :: 14-Dec-2015 :: [Release]
38 | Version 2.0 :: 18-Jun-2017 :: [Change] :: Full rework
39 | .LINK
40 | https://ps1code.com/2015/12/14/copy-vmware-vm-notes-2-comp-descr
41 | #>
42 |
43 | [CmdletBinding()]
44 | [OutputType([PSCustomObject])]
45 | Param (
46 | [Parameter(Mandatory, ValueFromPipeline)]
47 | [VMware.VimAutomation.ViCore.Types.V1.Inventory.Cluster]$Cluster
48 | ,
49 | [Parameter(Mandatory = $false)]
50 | [Alias("AddString")]
51 | [string]$CustomString = ' | VMware :: '
52 | ,
53 | [Parameter(Mandatory = $false)]
54 | [ValidateScript({Test-Path ($_ |Split-Path) -PathType 'Container'})]
55 | [string]$HelperCsv = "$PSScriptRoot\VMNotes.csv"
56 | ,
57 | [Parameter(Mandatory = $false, Position = 0)]
58 | [ValidateSet("ALL", "AD", "Local")]
59 | [Alias("Environment")]
60 | [string]$TargetEnv = 'ALL'
61 | ,
62 | [Parameter(Mandatory = $false)]
63 | [switch]$ErrorOnly
64 | )
65 |
66 | Begin
67 | {
68 | $rgxErrorMsg = '^(?.+?)(\.|$)'
69 | $rgxTruncate = '^(.+?)(\.|$)'
70 | $WarningPreference = 'SilentlyContinue'
71 | $ErrorActionPreference = 'Stop'
72 | $ScriptName = '{0}' -f $MyInvocation.MyCommand
73 | Write-Verbose "$ScriptName started at [$(Get-Date)]"
74 | }
75 | Process
76 | {
77 | $StatErrLocal = 0
78 | $StatErrAD = 0
79 |
80 | ### Export/Import Cluster ###
81 | Write-Progress -Activity "Export VM Notes" -Status "Cluster [$($Cluster.Name)]" -CurrentOperation "Exporting to [$HelperCsv] ..."
82 | $Cluster | Get-VM -vb:$false | select Name, Notes,
83 | @{ N = 'GuestFamily'; E = { $_.ExtensionData.Guest.GuestFamily } },
84 | @{ N = 'GuestHostname'; E = { $_.Guest.HostName } } |
85 | ? { $_.Notes -and $_.GuestFamily -eq 'windowsGuest' } |
86 | select * -exclude GuestFamily | sort Name | epcsv -NoTypeInformation -Encoding utf8 $HelperCsv
87 | $import = ipcsv -Encoding utf8 $HelperCsv
88 | Write-Progress -Completed $true -Status "Please wait"
89 |
90 | ### Copy to AD ###
91 | if ('ALL', 'AD' -contains $TargetEnv)
92 | {
93 | for ($i = 0; $i -lt $import.Length; $i++)
94 | {
95 | $VMGuestFqdn = if ('localhost', $null -notcontains $import[$i].GuestHostname) { $import[$i].GuestHostname } else { $import[$i].Name }
96 | $VMGuestHostname = [regex]::Match($VMGuestFqdn, $rgxTruncate).Groups[1].Value
97 |
98 | Try
99 | {
100 | Set-ADComputer -Identity $VMGuestHostname -Description "$($import[$i].Notes)$CustomString$($Cluster.Name)" -Confirm:$false
101 | Write-Progress -Activity "Set AD Computer Account Description" `
102 | -Status "Cluster [$($Cluster.Name)]" `
103 | -CurrentOperation "Current CN [$VMGuestHostname]" `
104 | -PercentComplete ($i/$import.Length*100)
105 | $ErrorMsg = ''
106 | }
107 | Catch
108 | {
109 | $ErrorMsg = [regex]::Match(("{0}" -f $Error.Exception.Message), $rgxErrorMsg).Groups[2].Value
110 | $StatErrAD += 1
111 | }
112 | Finally
113 | {
114 | $return = [pscustomobject] @{
115 | VM = $import[$i].Name
116 | ComputerName = $VMGuestHostname
117 | Cluster = $Cluster.Name
118 | Env = 'AD'
119 | VMNotes = $import[$i].Notes
120 | Error = $ErrorMsg
121 | }
122 | if ($ErrorOnly) { if ($return.Error) { $return } } else { $return }
123 | }
124 | }
125 | Write-Progress -Completed $true -Status "Please wait"
126 | }
127 |
128 | ### Copy to Local ###
129 | if ('ALL', 'Local' -contains $TargetEnv)
130 | {
131 | for ($i = 0; $i -lt $import.Length; $i++)
132 | {
133 | $VMGuestHostname = if ('localhost', $null -notcontains $import[$i].GuestHostname) { $import[$i].GuestHostname } else { $import[$i].Name }
134 |
135 | Try
136 | {
137 | $CN = Get-WmiObject -Class Win32_OperatingSystem -ComputerName $VMGuestHostname
138 | $CN.Description = "$($import[$i].Notes)$CustomString$($Cluster.Name)"
139 | $null = $CN.Put()
140 | Write-Progress -Activity "Set Remote Computer Description" `
141 | -Status "Cluster [$($Cluster.Name)]" `
142 | -CurrentOperation "Current Computer [$VMGuestHostname]" `
143 | -PercentComplete ($i/$import.Length*100)
144 | $ErrorMsg = ''
145 | }
146 | Catch
147 | {
148 | $ErrorMsg = [regex]::Match(("{0}" -f $Error.Exception.Message), $rgxErrorMsg).Groups[2].Value
149 | $StatErrLocal += 1
150 | }
151 | Finally
152 | {
153 | $return = [pscustomobject] @{
154 | VM = $import[$i].Name
155 | ComputerName = $VMGuestHostname
156 | Cluster = $Cluster.Name
157 | Env = 'Local'
158 | VMNotes = $import[$i].Notes
159 | Error = $ErrorMsg
160 | }
161 | if ($ErrorOnly) { if ($return.Error) { $return } } else { $return }
162 | }
163 | }
164 | }
165 | Write-Verbose "$ScriptName statistic: Cluster: [$($Cluster.Name)]; Total VM: [$($import.Count)]; Failed AD: [$StatErrAD]; Failed Local: [$StatErrLocal]"
166 | }
167 | End
168 | {
169 | Write-Verbose "$ScriptName finished at [$(Get-Date)]"
170 | }
171 |
--------------------------------------------------------------------------------
/Scripts/Find-VC.ps1:
--------------------------------------------------------------------------------
1 | #requires -version 3.0
2 |
3 | <#
4 | .SYNOPSIS
5 | Search VC's VM throw direct connection to group of ESXi Hosts.
6 | .DESCRIPTION
7 | This script generate list of ESXi Hosts with common suffix in name,
8 | e.g. (esxprod1,esxprod2, ...) or (esxdev01,esxdev02, ...) etc. and
9 | search VCenter's VM throw direct connection to this group of ESXi Hosts.
10 | .PARAMETER VC
11 | VC's VM Name.
12 | .PARAMETER HostSuffix
13 | ESXi Hosts' common suffix.
14 | .PARAMETER PostfixStart
15 | ESXi Hosts' postfix number start.
16 | .PARAMETER PostfixEnd
17 | ESXi Hosts' postfix number end.
18 | .PARAMETER AddZero
19 | Add ESXi Hosts' postfix leading zero to one-digit postfix (from 01 to 09).
20 | .EXAMPLE
21 | C:\PS> .\Find-VC.ps1 vc1 esxprod 1 20 -AddZero
22 | .EXAMPLE
23 | C:\PS> .\Find-VC.ps1 -VC vc1 -HostSuffix esxdev -PostfixEnd 6
24 | .EXAMPLE
25 | C:\PS> .\Find-VC.ps1 vc1 esxprod |fl
26 | .NOTES
27 | Author: Roman Gelman.
28 | .OUTPUTS
29 | PSCustomObject with two Properties: VC,VMHost or $null.
30 | .LINK
31 | https://ps1code.com
32 | #>
33 |
34 | Param (
35 |
36 | [Parameter(Mandatory=$true,Position=1,HelpMessage="vCenter's VM Name")]
37 | [Alias("vCenter")]
38 | [System.String]$VC
39 | ,
40 | [Parameter(Mandatory=$true,Position=2,HelpMessage="ESXi Hosts' common suffix")]
41 | [Alias("VMHostSuffix","ESXiSuffix")]
42 | [System.String]$HostSuffix
43 | ,
44 | [Parameter(Mandatory=$false,Position=3,HelpMessage="ESXi Hosts' postfix number start")]
45 | [ValidateRange(1,98)]
46 | [Alias("PostfixFirst","Start")]
47 | [Int]$PostfixStart = 1
48 | ,
49 | [Parameter(Mandatory=$false,Position=4,HelpMessage="ESXi Hosts' postfix number end")]
50 | [ValidateRange(2,99)]
51 | [Alias("PostfixLast","End")]
52 | [Int]$PostfixEnd = 9
53 | ,
54 | [Parameter(Mandatory=$false,Position=5,HelpMessage="Add ESXi Hosts' postfix leading zero")]
55 | [Switch]$AddZero = $false
56 | )
57 |
58 | Begin {
59 |
60 | Set-PowerCLIConfiguration -DefaultVIServerMode Multiple -Scope Session -Confirm:$false |Out-Null
61 | If ($PostfixEnd -le $PostfixStart) {Throw "PostfixEnd must be greater than PostfixStart"}
62 | }
63 |
64 | Process {
65 |
66 | $VMHostName = ''
67 | $cred = Get-Credential -UserName root -Message "Common VMHost Credentials"
68 | If ($cred) {
69 | $hosts = @()
70 |
71 | For ($i=$PostfixStart; $i -le $PostfixEnd; $i++) {
72 | If ($AddZero -and $i -match '^\d{1}$') {
73 | $hosts += $HostSuffix + '0' + $i
74 | } Else {
75 | $hosts += $HostSuffix + $i
76 | }
77 | }
78 | Connect-VIServer $hosts -WarningAction SilentlyContinue -ErrorAction SilentlyContinue -Credential $cred |select Name,IsConnected |ft -AutoSize
79 | If ($global:DefaultVIServers.Length -ne 0) {
80 | $VMHostName = (Get-VM -ErrorAction SilentlyContinue |? {$_.Name -eq $VC} |select -ExpandProperty VMHost).Name
81 | Disconnect-VIServer -Server '*' -Force -Confirm:$false
82 | }
83 | }
84 | }
85 |
86 | End {
87 |
88 | If ($VMHostName) {
89 | $Properties = [ordered]@{
90 | VC = $VC
91 | VMHost = $VMHostName
92 | }
93 | $Object = New-Object PSObject -Property $Properties
94 | return $Object
95 | }
96 | Else {return $null}
97 | }
98 |
--------------------------------------------------------------------------------
/Scripts/README.md:
--------------------------------------------------------------------------------
1 | # 
2 |
3 | ### VMware PowerCLi Scripts
4 |
5 | The links to appropriate articles are placed on the [main](https://github.com/rgel/PowerCLi) repository page.
6 |
--------------------------------------------------------------------------------
/VAMI/README.md:
--------------------------------------------------------------------------------
1 | # $${\color{green}VMware \space VAMI \space Automation \space Module}$$
2 |
3 | > [!NOTE]
4 | > PowerShell `3` or above is required\
5 | > To check, type the following: `$PSVersionTable.PSVersion.Major`
6 |
7 | To install this module, drop the entire `VAMI` folder into one of your module directories
8 |
9 | The default PowerShell module paths are listed in the `$env:PSModulePath` environment variable
10 |
11 | To make it look better, split the paths in this manner: `$env:PSModulePath -split ';'`
12 |
13 | The default per-user module path is: `"$env:HOMEDRIVE$env:HOMEPATH\Documents\WindowsPowerShell\Modules"`
14 |
15 | The default computer-level module path is: `"$env:windir\System32\WindowsPowerShell\v1.0\Modules"`
16 |
17 | To use the module, type following command: `Import-Module VAMI -Force -Verbose`
18 |
19 | To see the commands imported, type `Get-Command -Module VAMI`
20 |
21 | For help on each individual cmdlet or function, run `Get-Help CmdletName -Full [-Online][-Examples]`
22 |
23 | |No|Cmdlet|Description|
24 | |----|----|----|
25 | |1|[Get-VAMIHealth](https://ps1code.com/2017/05/11/vami-powercli-module)|Get Appliance health summary|
26 | |2|[Get-VAMISummary](https://ps1code.com/2017/12/10/vcsa-backup-expiration-powercli)|Get basic Appliance info|
27 | |3|[Get-VAMIAccess/Set-VAMIAccess](https://ps1code.com/2017/05/11/vami-powercli-module)|Get & `New!`Enable/Disable access interfaces (SSH, Shell, Console & DCUI)|
28 | |4|[Get-VAMIBackupSize](https://ps1code.com/2017/05/11/vami-powercli-module)|Get estimated backup size|
29 | |5|[Get-VAMIDisks](https://ps1code.com/2017/05/11/vami-powercli-module)|Get VMDK disk number to OS partition mapping|
30 | |6|[Get-VAMIStorageUsed/Start-VAMIDiskResize](https://ps1code.com/2017/05/11/vami-powercli-module)|Get OS partition usage & Resize partition|
31 | |7|[Get-VAMINetwork](https://ps1code.com/2017/05/11/vami-powercli-module)|Get networking info|
32 | |8|[Get-VAMIPerformance](https://ps1code.com/2017/05/11/vami-powercli-module)|Get CPU% & Memory% usage|
33 | |9|[Get-VAMIService](https://ps1code.com/2017/05/11/vami-powercli-module)|View Appliance services state|
34 | |10|[Restart-VAMIService/Start-VAMIService/Stop-VAMIService](https://ps1code.com/2017/05/11/vami-powercli-module)|Manage Appliance services|
35 | |11|[Get-VAMIStatsList](https://ps1code.com/2017/05/11/vami-powercli-module)|Get available monitoring metrics|
36 | |12|[Get-VAMITime](https://ps1code.com/2017/05/11/vami-powercli-module)|Get current Time and NTP info|
37 | |13|[Get-VAMIUser/New-VAMIUser/Remove-VAMIUser](https://ps1code.com/2017/05/11/vami-powercli-module)|Manage local users|
38 | |14|Stop-VAMIAppliance|Shutdown/Reboot VMware Appliance|
39 | |15|Suspend-VAMIShutdown|Cancel pending VMware Appliance reboot/shutdown|
40 |
--------------------------------------------------------------------------------
/VAMI/VAMI.psd1:
--------------------------------------------------------------------------------
1 | #
2 | # Module manifest for module 'VAMI'
3 | #
4 | # Generated by: Roman Gelman @rgelman75
5 | #
6 | # Generated on: 1/17/2019
7 | #
8 |
9 | @{
10 |
11 | # Script module or binary module file associated with this manifest.
12 | RootModule = 'VAMI'
13 |
14 | # Version number of this module.
15 | ModuleVersion = '2.0.0.0'
16 |
17 | # ID used to uniquely identify this module
18 | GUID = '0713482a-268f-424f-acfb-4aa0ea4f1310'
19 |
20 | # Author of this module
21 | Author = 'Roman Gelman @rgelman75'
22 |
23 | # Company or vendor of this module
24 | CompanyName = 'Taldor Israel'
25 |
26 | # Copyright statement for this module
27 | Copyright = '(c) 2019 Roman Gelman @rgelman75. All rights reserved.'
28 |
29 | # Description of the functionality provided by this module
30 | Description = 'VMware VAMI Management Module'
31 |
32 | # Minimum version of the Windows PowerShell engine required by this module
33 | PowerShellVersion = '3.0'
34 |
35 | # Name of the Windows PowerShell host required by this module
36 | # PowerShellHostName = ''
37 |
38 | # Minimum version of the Windows PowerShell host required by this module
39 | # PowerShellHostVersion = ''
40 |
41 | # Minimum version of Microsoft .NET Framework required by this module
42 | # DotNetFrameworkVersion = ''
43 |
44 | # Minimum version of the common language runtime (CLR) required by this module
45 | # CLRVersion = ''
46 |
47 | # Processor architecture (None, X86, Amd64) required by this module
48 | # ProcessorArchitecture = ''
49 |
50 | # Modules that must be imported into the global environment prior to importing this module
51 | # RequiredModules = @()
52 |
53 | # Assemblies that must be loaded prior to importing this module
54 | # RequiredAssemblies = @()
55 |
56 | # Script files (.ps1) that are run in the caller's environment prior to importing this module.
57 | # ScriptsToProcess = @()
58 |
59 | # Type files (.ps1xml) to be loaded when importing this module
60 | # TypesToProcess = @()
61 |
62 | # Format files (.ps1xml) to be loaded when importing this module
63 | # FormatsToProcess = @()
64 |
65 | # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess
66 | # NestedModules = @()
67 |
68 | # Functions to export from this module
69 | FunctionsToExport = '*'
70 |
71 | # Cmdlets to export from this module
72 | CmdletsToExport = '*'
73 |
74 | # Variables to export from this module
75 | VariablesToExport = '*'
76 |
77 | # Aliases to export from this module
78 | AliasesToExport = '*'
79 |
80 | # DSC resources to export from this module
81 | # DscResourcesToExport = @()
82 |
83 | # List of all modules packaged with this module
84 | # ModuleList = @()
85 |
86 | # List of all files packaged with this module
87 | # FileList = @()
88 |
89 | # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell.
90 | PrivateData = @{
91 |
92 | PSData = @{
93 |
94 | # Tags applied to this module. These help with module discovery in online galleries.
95 | # Tags = @()
96 |
97 | # A URL to the license for this module.
98 | # LicenseUri = ''
99 |
100 | # A URL to the main website for this project.
101 | ProjectUri = 'http://ps1code.com/2017/05/11/vami-powercli-module'
102 |
103 | # A URL to an icon representing this module.
104 | # IconUri = ''
105 |
106 | # ReleaseNotes of this module
107 | # ReleaseNotes = ''
108 |
109 | } # End of PSData hashtable
110 |
111 | } # End of PrivateData hashtable
112 |
113 | # HelpInfo URI of this module
114 | # HelpInfoURI = ''
115 |
116 | # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix.
117 | # DefaultCommandPrefix = ''
118 |
119 | }
120 |
121 |
--------------------------------------------------------------------------------
/VSAN/README.md:
--------------------------------------------------------------------------------
1 | # $${\color{green}VMware \space VSAN \space Management \space Module}$$
2 |
3 | > [!NOTE]
4 | > PowerShell `3` or above is required\
5 | > To check, type the following: `$PSVersionTable.PSVersion.Major`
6 |
7 | To install this module, drop the entire `VSAN` folder into one of your module directories
8 |
9 | The default PowerShell module paths are listed in the `$env:PSModulePath` environment variable
10 |
11 | To make it look better, split the paths in this manner: `$env:PSModulePath -split ';'`
12 |
13 | The default per-user module path is: `"$env:HOMEDRIVE$env:HOMEPATH\Documents\WindowsPowerShell\Modules"`
14 |
15 | The default computer-level module path is: `"$env:windir\System32\WindowsPowerShell\v1.0\Modules"`
16 |
17 | To use the module, type following command: `Import-Module VSAN -Force -Verbose`
18 |
19 | To see the commands imported, type `Get-Command -Module VSAN`
20 |
21 | For help on each individual cmdlet or function, run `Get-Help CmdletName -Full [-Online][-Examples]`
22 |
23 | |No|Cmdlet|Description|
24 | |----|----|----|
25 | |1|[Get-VSANHealthCheckSupported](https://ps1code.com/2017/05/08/vsan-health-check)|Get all available VSAN Health Checks. [Idea](http://www.virtuallyghetto.com/2017/04/managing-silencing-vsan-health-checks-using-powercli.html#more-22754) by William Lam|
26 | |2|[Get-VSANHealthCheckSkipped](https://ps1code.com/2017/05/08/vsan-health-check)|Get skipped VSAN Health Checks. [Idea](http://www.virtuallyghetto.com/2017/04/managing-silencing-vsan-health-checks-using-powercli.html#more-22754) by William Lam|
27 | |3|[Get-VSANHealthCheckGroup](https://ps1code.com/2017/05/08/vsan-health-check)|Get all VSAN Health Check groups. [Idea](http://www.virtuallyghetto.com/2017/04/managing-silencing-vsan-health-checks-using-powercli.html#more-22754) by William Lam|
28 | |4|[Enable-VSANHealthCheckSkipped](https://ps1code.com/2017/05/08/vsan-health-check)|Enable skipped VSAN Health Check(s). [Idea](http://www.virtuallyghetto.com/2017/04/managing-silencing-vsan-health-checks-using-powercli.html#more-22754) by William Lam|
29 | |5|[Disable-VSANHealthCheck](https://ps1code.com/2017/05/08/vsan-health-check)|Disable VSAN Health Check(s). [Idea](http://www.virtuallyghetto.com/2017/04/managing-silencing-vsan-health-checks-using-powercli.html#more-22754) by William Lam|
30 | |6|[Get-VSANSmartData](https://ps1code.com/2017/05/08/vsan-health-check)|`Update!`Get S.M.A.R.T drive data. [Idea](http://www.virtuallyghetto.com/2017/04/smart-drive-data-now-available-using-vsan-management-6-6-api.html) by William Lam|
31 | |7|[Get-VSANVersion](https://ps1code.com/2017/05/08/vsan-health-check)|Get VSAN health service version. [Idea](http://www.virtuallyghetto.com/2017/04/getting-started-wthe-new-powercli-6-5-1-get-vsanview-cmdlet.html) by William Lam|
32 | |8|[Get-VSANHealthSummary](https://ps1code.com/2017/05/08/vsan-health-check)|Fetch VSAN Cluster Health status|
33 | |9|[Invoke-VSANHealthCheck](https://ps1code.com/2017/05/08/vsan-health-check)|Run VSAN Cluster Health Test|
34 | |10|[Get-VSANCapability](https://ps1code.com/2017/07/19/vsan-capabilities)|Get VSAN capabilities|
35 | |11|Get-VSANUsage|Get VSAN Datastore usage. [Idea](https://www.virtuallyghetto.com/2018/06/retrieving-detailed-per-vm-space-utilization-on-vsan.html) by William Lam|
36 | |12|Get-VSANLimit|Get VSAN Cluster limits. [Idea](http://www.virtuallyghetto.com/2017/06/how-to-convert-vsan-rvc-commands-into-powercli-andor-other-vsphere-sdks.html) by William Lam|
37 |
--------------------------------------------------------------------------------
/VSAN/VSAN.psd1:
--------------------------------------------------------------------------------
1 | #
2 | # Module manifest for module 'VSAN'
3 | #
4 | # Generated by: Roman Gelman @rgelman75
5 | #
6 | # Generated on: 1/10/2019
7 | #
8 |
9 | @{
10 |
11 | # Script module or binary module file associated with this manifest.
12 | RootModule = 'VSAN'
13 |
14 | # Version number of this module.
15 | ModuleVersion = '1.0.0.7'
16 |
17 | # ID used to uniquely identify this module
18 | GUID = '89b4b1b0-e699-477d-a712-615bfaabeb3d'
19 |
20 | # Author of this module
21 | Author = 'Roman Gelman @rgelman75'
22 |
23 | # Company or vendor of this module
24 | CompanyName = 'Taldor Israel'
25 |
26 | # Copyright statement for this module
27 | Copyright = '(c) 2019 Roman Gelman @rgelman75. All rights reserved.'
28 |
29 | # Description of the functionality provided by this module
30 | Description = 'VMware VSAN Management Module'
31 |
32 | # Minimum version of the Windows PowerShell engine required by this module
33 | PowerShellVersion = '3.0'
34 |
35 | # Name of the Windows PowerShell host required by this module
36 | # PowerShellHostName = ''
37 |
38 | # Minimum version of the Windows PowerShell host required by this module
39 | # PowerShellHostVersion = ''
40 |
41 | # Minimum version of Microsoft .NET Framework required by this module
42 | # DotNetFrameworkVersion = ''
43 |
44 | # Minimum version of the common language runtime (CLR) required by this module
45 | # CLRVersion = ''
46 |
47 | # Processor architecture (None, X86, Amd64) required by this module
48 | # ProcessorArchitecture = ''
49 |
50 | # Modules that must be imported into the global environment prior to importing this module
51 | # RequiredModules = @()
52 |
53 | # Assemblies that must be loaded prior to importing this module
54 | # RequiredAssemblies = @()
55 |
56 | # Script files (.ps1) that are run in the caller's environment prior to importing this module.
57 | # ScriptsToProcess = @()
58 |
59 | # Type files (.ps1xml) to be loaded when importing this module
60 | # TypesToProcess = @()
61 |
62 | # Format files (.ps1xml) to be loaded when importing this module
63 | # FormatsToProcess = @()
64 |
65 | # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess
66 | # NestedModules = @()
67 |
68 | # Functions to export from this module
69 | FunctionsToExport = '*'
70 |
71 | # Cmdlets to export from this module
72 | CmdletsToExport = '*'
73 |
74 | # Variables to export from this module
75 | VariablesToExport = '*'
76 |
77 | # Aliases to export from this module
78 | AliasesToExport = '*'
79 |
80 | # DSC resources to export from this module
81 | # DscResourcesToExport = @()
82 |
83 | # List of all modules packaged with this module
84 | # ModuleList = @()
85 |
86 | # List of all files packaged with this module
87 | # FileList = @()
88 |
89 | # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell.
90 | PrivateData = @{
91 |
92 | PSData = @{
93 |
94 | # Tags applied to this module. These help with module discovery in online galleries.
95 | # Tags = @()
96 |
97 | # A URL to the license for this module.
98 | # LicenseUri = ''
99 |
100 | # A URL to the main website for this project.
101 | ProjectUri = 'http://www.ps1code.com/category/vmware-powercli/vsan'
102 |
103 | # A URL to an icon representing this module.
104 | # IconUri = ''
105 |
106 | # ReleaseNotes of this module
107 | # ReleaseNotes = ''
108 |
109 | } # End of PSData hashtable
110 |
111 | } # End of PrivateData hashtable
112 |
113 | # HelpInfo URI of this module
114 | # HelpInfoURI = ''
115 |
116 | # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix.
117 | # DefaultCommandPrefix = ''
118 |
119 | }
120 |
121 |
--------------------------------------------------------------------------------
/VSAN/VSAN.psm1:
--------------------------------------------------------------------------------
1 | Function Get-VSANVersion
2 | {
3 |
4 | <#
5 | .SYNOPSIS
6 | Get vSAN health service version.
7 | .DESCRIPTION
8 | This function retreives vSAN health service version at the vCenter Server level as well as for the individual ESXi host(s).
9 | .PARAMETER VsanCluster
10 | Specifies a vSAN Cluster object(s), returned by Get-Cluster cmdlet.
11 | .EXAMPLE
12 | PS C:\> Get-Cluster |Get-VSANVersion -Verbose
13 | .EXAMPLE
14 | PS C:\> Get-Cluster VSAN-Cluster |Get-VSANVersion |sort Version
15 | .NOTES
16 | Idea :: William Lam @lamw
17 | Author :: Roman Gelman @rgelman75
18 | Requirement :: PowerCLI 6.5.1, VSAN 6.6
19 | Version 1.0 :: 26-Apr-2017 :: [Release] :: Publicly available
20 | Version 1.1 :: 20-Jul-2017 :: [Bugfix] :: The '$global:DefaultVIServers' variable used instead of '$global:DefaultVIServer' to determine VC name
21 | Version 1.2 :: 20-Jul-2017 :: [Improve] :: The 'Version' property type changed from [string] to [System.Version], the 'Cluster' property added
22 | Version 1.3 :: 20-Jul-2017 :: [Improve] :: Returned object standardized to [PSCustomObject] data type
23 | Version 1.4 :: 19-Dec-2018 :: [Improve] :: Added 'Type' property
24 | .LINK
25 | http://www.virtuallyghetto.com/2017/04/getting-started-wthe-new-powercli-6-5-1-get-vsanview-cmdlet.html
26 | #>
27 |
28 | [CmdletBinding()]
29 | [OutputType([PSCustomObject])]
30 | Param (
31 | [Parameter(Mandatory, ValueFromPipeline)]
32 | [Alias("Cluster")]
33 | [VMware.VimAutomation.ViCore.Types.V1.Inventory.Cluster]$VsanCluster
34 | )
35 |
36 | Begin
37 | {
38 | $vchs = Get-VsanView -Id "VsanVcClusterHealthSystem-vsan-cluster-health-system" -Verbose:$false
39 | }
40 | Process
41 | {
42 | if ($VsanCluster.VsanEnabled)
43 | {
44 | $ClusterVc = [regex]::Match($VsanCluster.Uid, '@(.+):\d+/').Groups[1].Value
45 | $result = $vchs.VsanVcClusterQueryVerifyHealthSystemVersions($VsanCluster.Id)
46 | $return = $result.HostResults | select Hostname, Version | sort Hostname
47 |
48 | ### Return Hosts' version ###
49 | $return | %{
50 | [pscustomobject] @{
51 | Cluster = $VsanCluster.Name
52 | Hostname = $_.Hostname
53 | Type = 'VMHost'
54 | Version = [version]$_.Version
55 | }
56 | }
57 |
58 | ### Return VC version ###
59 | $global:DefaultVIServers | %{
60 | if ($_.Name -eq $ClusterVc)
61 | {
62 | [pscustomobject] @{
63 | Cluster = $VsanCluster.Name
64 | Hostname = $_.Name
65 | Type = 'VC'
66 | Version = [version]$result.VcVersion
67 | }
68 | }
69 | }
70 | }
71 | else
72 | {
73 | Write-Verbose "The [$($VsanCluster.Name)] cluster is not VSAN Enabled"
74 | }
75 | }
76 | End { }
77 |
78 | } #EndFunction Get-VSANVersion
79 |
80 | Function Get-VSANHealthCheckGroup
81 | {
82 |
83 | <#
84 | .SYNOPSIS
85 | Get all vSAN Health Check groups.
86 | .DESCRIPTION
87 | This function retreives the list of vSAN Health Check groups (categories).
88 | .EXAMPLE
89 | PS C:\> Get-VSANHealthCheckGroup
90 | .NOTES
91 | Idea :: William Lam @lamw
92 | Author :: Roman Gelman @rgelman75
93 | Requirement :: PowerCLI 6.5.1, VSAN 6.6
94 | Version 1.0 :: 26-Apr-2017 :: [Release] :: Publicly available
95 | .LINK
96 | http://www.virtuallyghetto.com/2017/04/managing-silencing-vsan-health-checks-using-powercli.html#more-22754
97 | #>
98 |
99 | $vchs = Get-VsanView -Id "VsanVcClusterHealthSystem-vsan-cluster-health-system"
100 | $vchs.VsanQueryAllSupportedHealthChecks() | sort -Property GroupName | select group* -Unique
101 |
102 | } #EndFunction Get-VSANHealthCheckGroup
103 |
104 | Function Get-VSANHealthCheckSupported
105 | {
106 |
107 | <#
108 | .SYNOPSIS
109 | Get all available vSAN Health Checks.
110 | .DESCRIPTION
111 | This function retreives all available vSAN Health Checks.
112 | .PARAMETER TestGroupId
113 | Specifies the Id of a vSAN Health Check group.
114 | .EXAMPLE
115 | PS C:\> Get-VSANHealthCheckSupported
116 | .EXAMPLE
117 | PS C:\> Get-VSANHealthCheckSupported -TestGroupId perfsvc
118 | .EXAMPLE
119 | PS C:\> Get-VSANHealthCheckSupported cloudhealth,hcl
120 | .NOTES
121 | Idea :: William Lam @lamw
122 | Edited by :: Roman Gelman @rgelman75
123 | Requirement :: PowerCLI 6.5.1, VSAN 6.6
124 | Version 1.0 :: 26-Apr-2017 :: [Release] :: Publicly available
125 | .LINK
126 | http://www.virtuallyghetto.com/2017/04/managing-silencing-vsan-health-checks-using-powercli.html#more-22754
127 | #>
128 |
129 | Param (
130 | [Parameter(Mandatory = $false)]
131 | ### PS C:\> "'" + ((Get-VSANHealthCheckGroup).GroupId -join "', '") + "'"
132 | [ValidateSet('cloudhealth', 'cluster', 'data', 'encryption', 'hcl', 'limits', 'network', 'perfsvc', 'physicaldisks', 'stretchedcluster', 'iscsi')]
133 | [string[]]$TestGroupId
134 | )
135 |
136 | $prop = @('TestId', 'TestName', 'GroupName')
137 | $vchs = Get-VsanView -Id "VsanVcClusterHealthSystem-vsan-cluster-health-system"
138 | $result = $vchs.VsanQueryAllSupportedHealthChecks() | sort -Property GroupName, TestId
139 |
140 | if ($PSBoundParameters.ContainsKey('TestGroupId')) { $result | ? { $TestGroupId -contains $_.GroupId } | select $prop }
141 | else { $result | select $prop }
142 |
143 | } #EndFunction Get-VSANHealthCheckSupported
144 |
145 | Function Get-VSANHealthCheckSkipped
146 | {
147 |
148 | <#
149 | .SYNOPSIS
150 | Get skipped vSAN Health Checks.
151 | .DESCRIPTION
152 | This function retreives the list of vSAN Health Checks that have been silenced.
153 | .PARAMETER VsanCluster
154 | Specifies a vSAN Cluster object(s), returned by Get-Cluster cmdlet.
155 | .EXAMPLE
156 | PS C:\> Get-VSANHealthCheckSkipped -Cluster (Get-Cluster VSAN-Cluster) -Verbose
157 | .EXAMPLE
158 | PS C:\> Get-Cluster |Get-VSANHealthCheckSkipped |sort GroupName,TestId
159 | .NOTES
160 | Idea :: William Lam @lamw
161 | Edited by :: Roman Gelman @rgelman75
162 | Requirement :: PowerCLI 6.5.1, VSAN 6.6
163 | Version 1.0 :: 26-Apr-2017 :: [Release] :: Publicly available
164 | .LINK
165 | http://www.virtuallyghetto.com/2017/04/managing-silencing-vsan-health-checks-using-powercli.html#more-22754
166 | #>
167 |
168 | [CmdletBinding()]
169 | Param (
170 | [Parameter(Mandatory, ValueFromPipeline)]
171 | [Alias("Cluster")]
172 | [VMware.VimAutomation.ViCore.Types.V1.Inventory.Cluster]$VsanCluster
173 | )
174 |
175 | Begin
176 | {
177 | $vchs = Get-VsanView -Id "VsanVcClusterHealthSystem-vsan-cluster-health-system" -Verbose:$false
178 | }
179 | Process
180 | {
181 | if ($VsanCluster.VsanEnabled)
182 | {
183 | $results = $vchs.VsanHealthGetVsanClusterSilentChecks($VsanCluster.Id)
184 | foreach ($result in $results)
185 | {
186 | $supported = Get-VSANHealthCheckSupported |? {$_.TestId -eq $result}
187 | [pscustomobject]@{
188 | Cluster = $VsanCluster.Name
189 | TestId = $result
190 | TestName = $supported.TestName
191 | GroupName = $supported.GroupName
192 | }
193 | }
194 | }
195 | else
196 | {
197 | Write-Verbose "The [$($VsanCluster.Name)] cluster is not VSAN Enabled"
198 | }
199 | }
200 | End {}
201 |
202 | } #EndFunction Get-VSANHealthCheckSkipped
203 |
204 | Function Enable-VSANHealthCheckSkipped
205 | {
206 |
207 | <#
208 | .SYNOPSIS
209 | Enable skipped vSAN Health Check(s).
210 | .DESCRIPTION
211 | This function enables the vSAN Health Checks that have been silenced (skipped).
212 | .PARAMETER Cluster
213 | Specifies the name of a vSAN Cluster.
214 | .PARAMETER TestId
215 | Specifies the vSAN Health Check Id to enable.
216 | .EXAMPLE
217 | PS C:\> Get-Cluster VSAN-Cluster |Get-VSANHealthCheckSkipped |Enable-VSANHealthCheckSkipped
218 | .EXAMPLE
219 | PS C:\> Get-Cluster |Get-VSANHealthCheckSkipped |? {$_.GroupName -eq 'Hardware compatibility'} |Enable-VSANHealthCheckSkipped -Confirm:$false -Verbose
220 | Enable all silenced vSAN Health Checks that belong to certain Group (Category) with no confirmation dialog.
221 | .EXAMPLE
222 | PS C:\> Get-Cluster |Get-VSANHealthCheckSkipped
223 | Review the changes after execution.
224 | .NOTES
225 | Idea :: William Lam @lamw
226 | Author :: Roman Gelman @rgelman75
227 | Requirement :: PowerCLI 6.5.1, VSAN 6.6
228 | Version 1.0 :: 26-Apr-2017 :: [Release] :: Publicly available
229 | .LINK
230 | http://www.virtuallyghetto.com/2017/04/managing-silencing-vsan-health-checks-using-powercli.html#more-22754
231 | #>
232 |
233 | [CmdletBinding(ConfirmImpact = 'High', SupportsShouldProcess)]
234 | [Alias("Enable-VSANHealthCheck")]
235 | Param (
236 | [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
237 | [string]$Cluster
238 | ,
239 | [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
240 | [string]$TestId
241 | )
242 |
243 | Begin
244 | {
245 | $ErrorActionPreference = 'Stop'
246 | $WarningPreference = 'SilentlyContinue'
247 | $vchs = Get-VsanView -Id "VsanVcClusterHealthSystem-vsan-cluster-health-system" -Verbose:$false
248 | } #EndBegin
249 |
250 | Process
251 | {
252 | if ($PSCmdlet.ShouldProcess("Cluster [$Cluster]", "Enable skipped vSAN Health Check [$TestId]"))
253 | {
254 | Try
255 | {
256 | $removeSilentChecks = $vchs.VsanHealthSetVsanClusterSilentChecks((Get-Cluster $Cluster -Verbose:$false).Id, $null, $TestId)
257 | }
258 | Catch
259 | {
260 | ("{0}" -f $Error.Exception.Message).ToString()
261 | }
262 | }
263 | } #EndProcess
264 |
265 | End { }
266 |
267 | } #EndFunction Enable-VSANHealthCheckSkipped
268 |
269 | Function Disable-VSANHealthCheck
270 | {
271 |
272 | <#
273 | .SYNOPSIS
274 | Disable vSAN Health Check(s).
275 | .DESCRIPTION
276 | This function skips (silences) the vSAN Health Checks.
277 | .PARAMETER VsanCluster
278 | Specifies a vSAN Cluster object(s), returned by Get-Cluster cmdlet.
279 | .PARAMETER TestId
280 | Specifies the vSAN Health Check Id to disable.
281 | .EXAMPLE
282 | PS C:\> Get-VSANHealthCheckSupported |Disable-VSANHealthCheck (Get-Cluster) -Confirm:$false -Verbose
283 | Disable !ALL! available vSAN Health Checks on !ALL! VSAN enabled clusters with no confirmation.
284 | Use in LAB environments only!
285 | .EXAMPLE
286 | PS C:\> Get-VSANHealthCheckSupported -TestGroupId perfsvc,hcl,limits |Disable-VSANHealthCheck (Get-Cluster VSAN-Cluster)
287 | Disable all vSAN Health Checks that belong to the certain groups (categories).
288 | .EXAMPLE
289 | PS C:\> Get-Cluster VSAN-Cluster |Get-VSANHealthCheckSkipped
290 | Review the changes after execution.
291 | .NOTES
292 | Idea :: William Lam @lamw
293 | Author :: Roman Gelman @rgelman75
294 | Requirement :: PowerCLI 6.5.1, VSAN 6.6
295 | Version 1.0 :: 26-Apr-2017 :: [Release] :: Publicly available
296 | .LINK
297 | http://www.virtuallyghetto.com/2017/04/managing-silencing-vsan-health-checks-using-powercli.html#more-22754
298 | #>
299 |
300 | [CmdletBinding(ConfirmImpact = 'High', SupportsShouldProcess)]
301 | Param (
302 | [Parameter(Mandatory, Position = 0)]
303 | [Alias("Cluster")]
304 | [VMware.VimAutomation.ViCore.Types.V1.Inventory.Cluster[]]$VsanCluster
305 | ,
306 | [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
307 | [string]$TestId
308 | )
309 |
310 | Begin
311 | {
312 | $ErrorActionPreference = 'Stop'
313 | $WarningPreference = 'SilentlyContinue'
314 | $vchs = Get-VsanView -Id "VsanVcClusterHealthSystem-vsan-cluster-health-system" -Verbose:$false
315 | } #EndBegin
316 |
317 | Process
318 | {
319 | foreach ($CL in $VsanCluster)
320 | {
321 | if ($CL.VsanEnabled)
322 | {
323 | if ($PSCmdlet.ShouldProcess("Cluster [$($CL.Name)]", "Disable vSAN Health Check [$TestId]"))
324 | {
325 | Try
326 | {
327 | $addSilentChecks = $vchs.VsanHealthSetVsanClusterSilentChecks($CL.Id, $TestId, $null)
328 | }
329 | Catch
330 | {
331 | ("{0}" -f $Error.Exception.Message).ToString()
332 | }
333 | }
334 | }
335 | }
336 | } #EndProcess
337 |
338 | End { }
339 |
340 | } #EndFunction Disable-VSANHealthCheck
341 |
342 | Function Get-VSANSmartData
343 | {
344 |
345 | <#
346 | .SYNOPSIS
347 | Get SMART drive data.
348 | .DESCRIPTION
349 | This function retreives S.M.A.R.T. (Self Monitoring, Analysis & Reporting Technology) drive data.
350 | .PARAMETER VsanCluster
351 | Specifies a vSAN Cluster object(s), returned by Get-Cluster cmdlet.
352 | .PARAMETER HideGood
353 | If specified, only thresholded attributes returned.
354 | .EXAMPLE
355 | PS C:\> Get-Cluster VSAN-Cluster | Get-VSANSmartData -Verbose
356 | .EXAMPLE
357 | PS C:\> Get-Cluster | Get-VSANSmartData -HideGood
358 | .EXAMPLE
359 | PS C:\> Get-Cluster | Get-VSANSmartData | ogv -Title 'S.M.A.R.T Data'
360 | .NOTES
361 | Idea :: William Lam @lamw
362 | Edited by :: Roman Gelman @rgelman75
363 | Requirement :: PowerCLI 6.5.1, PowerShell 4.0, VSAN 6.6
364 | Version 1.0 :: 26-Apr-2017 :: [Release] :: Publicly available
365 | Version 1.1 :: 08-Jan-2019 :: [Bugfix] :: Blank statistics returned
366 | Version 1.2 :: 09-Jan-2019 :: [Change] :: Added a lot of new properties
367 | Version 1.3 :: 10-Jan-2019 :: [Change] :: Added -HideGood parameter
368 | .LINK
369 | http://www.virtuallyghetto.com/2017/04/smart-drive-data-now-available-using-vsan-management-6-6-api.html
370 | #>
371 |
372 | [CmdletBinding()]
373 | Param (
374 | [Parameter(Mandatory, ValueFromPipeline)]
375 | [Alias("Cluster")]
376 | [VMware.VimAutomation.ViCore.Types.V1.Inventory.Cluster]$VsanCluster
377 | ,
378 | [switch]$HideGood
379 | )
380 |
381 | Begin
382 | {
383 | $vchs = Get-VsanView -Id "VsanVcClusterHealthSystem-vsan-cluster-health-system" -Verbose:$false
384 | }
385 | Process
386 | {
387 | if ($VsanCluster.VsanEnabled)
388 | {
389 | $results = $vchs.VsanQueryVcClusterSmartStatsSummary($VsanCluster.Id)
390 |
391 | foreach ($VMHost in ($results | sort Hostname))
392 | {
393 | $Host = Get-VMHost $VMHost.Hostname -Verbose:$false
394 | $VMHostName = if ($VMHost.Hostname -match '[a-zA-Z]') { [regex]::Match($VMHost.Hostname, '^(.+?)(\.|$)').Groups[1].Value } else { $VMHost.Hostname }
395 |
396 | foreach ($SmartStat in $VMHost.SmartStats.Where{ $null -ne $_.Stats })
397 | {
398 | $DiskInfo = $Host.StorageInfo.ExtensionData.StorageDeviceInfo.ScsiLun.Where{ $_.CanonicalName -eq $SmartStat.Disk }
399 |
400 | foreach ($Stat in $SmartStat.Stats.Where{ $_.Threshold -ne $null })
401 | {
402 | $ParameterType = if ($Stat.Threshold -ne 0)
403 | {
404 | 'Critical'
405 | $IsFailed = if ($Stat.Worst -le $Stat.Threshold) { $true } else { $false }
406 | }
407 | else
408 | {
409 | 'Info'
410 | $IsFailed = $false
411 | }
412 |
413 | $Healthy = if ($Stat.Value -ne 100) { [math]::Round($Stat.Value * 100 / (253 - $Stat.Threshold), 0) } else { 100 }
414 |
415 | $return = [pscustomobject]@{
416 | Cluster = $VsanCluster.Name
417 | VMHost = $VMHostName
418 | Disk = $SmartStat.Disk
419 | IsSSD = $DiskInfo.SSD
420 | DiskModel = $DiskInfo.Model
421 | DiskState = $DiskInfo.OperationalState
422 | Attribute = (Get-Culture).TextInfo.ToTitleCase(($Stat.Parameter -replace 'smart', $null))
423 | Type = $ParameterType
424 | Value = $Stat.Value
425 | Worst = $Stat.Worst
426 | Threshold = $Stat.Threshold
427 | 'Health%' = $Healthy
428 | DiskFail = $IsFailed
429 | }
430 | if ($HideGood)
431 | {
432 | if ($return.DiskFail) { $return }
433 | }
434 | else { $return }
435 | }
436 | }
437 | }
438 | }
439 | else
440 | {
441 | Write-Verbose "The [$($VsanCluster.Name)] cluster is not VSAN Enabled"
442 | }
443 | }
444 | End { }
445 |
446 | } #EndFunction Get-VSANSmartData
447 |
448 | Function Get-VSANHealthSummary
449 | {
450 |
451 | <#
452 | .SYNOPSIS
453 | Fetch vSAN Cluster Health Status.
454 | .DESCRIPTION
455 | This function performs a cluster wide health check across all types of Health Checks.
456 | .PARAMETER VsanCluster
457 | Specifies a vSAN Cluster object(s), returned by Get-Cluster cmdlet.
458 | .PARAMETER FetchFromCache
459 | If specified the results are returned from cache directly instead of running the full health check.
460 | .PARAMETER SummaryLevel
461 | Specifies Health Check sets. If Strict level selected, the Best Practices Health Checks will be taken.
462 | .EXAMPLE
463 | PS C:\> Get-Cluster VSAN-Cluster |Get-VSANHealthSummary Strict -Verbose
464 | .EXAMPLE
465 | PS C:\> Get-Cluster |Get-VSANHealthSummary -FetchFromCache |ft -Property cluster,*health -au
466 | .NOTES
467 | Author :: Roman Gelman @rgelman75
468 | Requirement :: PowerCLI 6.5.1, VSAN 6.6
469 | Version 1.0 :: 27-Apr-2017 :: [Release] :: Publicly available
470 | .LINK
471 | https://ps1code.com/2017/05/08/vsan-health-check
472 | #>
473 |
474 | [CmdletBinding()]
475 | Param (
476 | [Parameter(Mandatory, ValueFromPipeline)]
477 | [Alias("Cluster")]
478 | [VMware.VimAutomation.ViCore.Types.V1.Inventory.Cluster]$VsanCluster
479 | ,
480 | [Parameter(Mandatory = $false)]
481 | [Alias("Cache")]
482 | [switch]$FetchFromCache
483 | ,
484 | [Parameter(Mandatory = $false, Position = 0)]
485 | [ValidateSet("Default", "Strict")]
486 | [Alias("Level")]
487 | [string]$SummaryLevel = 'Default'
488 | )
489 |
490 | Begin
491 | {
492 | $vchs = Get-VsanView -Id "VsanVcClusterHealthSystem-vsan-cluster-health-system" -Verbose:$false
493 | $fromCache = if ($FetchFromCache) { $true } else { $false }
494 | $perspective = if ($SummaryLevel -eq 'Default') { 'defaultView' } else { 'deployAssist' }
495 | $FormatDate = "dd'/'MM'/'yyyy HH':'mm':'ss"
496 | }
497 | Process
498 | {
499 | if ($VsanCluster.VsanEnabled)
500 | {
501 | $result = $vchs.VsanQueryVcClusterHealthSummary($VsanCluster.Id, 2, $null, $true, $null, $fromCache, $perspective)
502 |
503 | $NetworkHealth = if ($result.NetworkHealth.IssueFound) {'Yellow'} else {'Green'}
504 |
505 | $summary = [pscustomobject]@{
506 | Cluster = $VsanCluster.Name
507 | OverallHealth = (Get-Culture).TextInfo.ToTitleCase($result.OverallHealth)
508 | OverallHealthDescr = $result.OverallHealthDescription
509 | Timestamp = (Get-Date $result.Timestamp).ToLocalTime().ToString($FormatDate)
510 | VMHealth = (Get-Culture).TextInfo.ToTitleCase($result.VmHealth.OverallHealthState)
511 | NetworkHealth = $NetworkHealth
512 | DiskHealth = (Get-Culture).TextInfo.ToTitleCase($result.PhysicalDisksHealth.OverallHealth)
513 | DiskSpaceHealth = (Get-Culture).TextInfo.ToTitleCase($result.LimitHealth.DiskFreeSpaceHealth)
514 | HclDbHealth = (Get-Culture).TextInfo.ToTitleCase($result.HclInfo.HclDbAgeHealth)
515 | HclDbTimestamp = (Get-Date $result.HclInfo.HclDbLastUpdate).ToLocalTime().ToString($FormatDate)
516 | }
517 |
518 | if ($null -ne $result.ClusterStatus.UntrackedHosts) { $summary | Add-Member -MemberType NoteProperty -Name VMHostUntracked -Value $result.ClusterStatus.UntrackedHosts }
519 | if ($null -ne $result.PhysicalDisksHealth.ComponentsWithIssues) { $summary | Add-Member -MemberType NoteProperty -Name DiskProblem -Value $result.PhysicalDisksHealth.ComponentsWithIssues }
520 | $summary
521 | }
522 | else
523 | {
524 | Write-Verbose "The [$($VsanCluster.Name)] cluster is not VSAN Enabled"
525 | }
526 | }
527 | End { }
528 |
529 | } #EndFunction Get-VSANHealthSummary
530 |
531 | Function Invoke-VSANHealthCheck
532 | {
533 |
534 | <#
535 | .SYNOPSIS
536 | Run vSAN Cluster Health Test.
537 | .DESCRIPTION
538 | This function performs a cluster wide health check across all types of Health Checks.
539 | .PARAMETER VsanCluster
540 | Specifies a vSAN Cluster object(s), returned by Get-Cluster cmdlet.
541 | .PARAMETER Level
542 | Specifies Health Check tests level. Available levels are Group or Test level.
543 | .PARAMETER HideGreen
544 | If specified, Green or Skipped Health Checks will be removed from the resultant report.
545 | .EXAMPLE
546 | PS C:\> Get-Cluster |Invoke-VSANHealthCheck -Verbose |sort Health
547 | .EXAMPLE
548 | PS C:\> Get-Cluster |Invoke-VSANHealthCheck -Level Test |select * -exclude descr* |sort TestGroup,Test |ft -au
549 | .EXAMPLE
550 | PS C:\> Get-Cluster VSAN-Cluster |Invoke-VSANHealthCheck Test -HideGreen |sort Health
551 | .NOTES
552 | Author :: Roman Gelman @rgelman75
553 | Requirement :: PowerCLI 6.5.1, VSAN 6.6
554 | Version 1.0 :: 30-Apr-2017 :: [Release] :: Publicly available
555 | .LINK
556 | https://ps1code.com/2017/05/08/vsan-health-check
557 | #>
558 |
559 | [CmdletBinding()]
560 | Param (
561 | [Parameter(Mandatory, ValueFromPipeline)]
562 | [Alias("Cluster")]
563 | [VMware.VimAutomation.ViCore.Types.V1.Inventory.Cluster]$VsanCluster
564 | ,
565 | [Parameter(Mandatory = $false, Position = 0)]
566 | [ValidateSet("Group", "Test")]
567 | [string]$Level = 'Group'
568 | ,
569 | [Parameter(Mandatory = $false)]
570 | [switch]$HideGreen
571 | )
572 |
573 | Begin
574 | {
575 | $vchs = Get-VsanView -Id "VsanVcClusterHealthSystem-vsan-cluster-health-system" -Verbose:$false
576 | }
577 | Process
578 | {
579 | if ($VsanCluster.VsanEnabled)
580 | {
581 | $result = $vchs.VsanQueryVcClusterHealthSummary($VsanCluster.Id, 2, $null, $true, $null, $false, 'defaultView')
582 |
583 | if ($Level -eq 'Group') {
584 | foreach ($Group in $result.Groups) {
585 | $obj = [pscustomobject] @{
586 | Cluster = $VsanCluster.Name
587 | TestGroup = $Group.GroupName
588 | Health = (Get-Culture).TextInfo.ToTitleCase($Group.GroupHealth)
589 | }
590 | if ($PSBoundParameters.ContainsKey('HideGreen'))
591 | {
592 | if ('green', 'skipped' -notcontains $obj.Health) { $obj }
593 | }
594 | else { $obj }
595 | }
596 | }
597 | else
598 | {
599 | foreach ($Group in $result.Groups)
600 | {
601 | foreach ($Test in $Group.GroupTests)
602 | {
603 | $obj = [pscustomobject] @{
604 | Cluster = $VsanCluster.Name
605 | TestGroup = $Group.GroupName
606 | Test = $Test.TestName
607 | Description = $Test.TestShortDescription
608 | Health = (Get-Culture).TextInfo.ToTitleCase($Test.TestHealth)
609 | }
610 | if ($PSBoundParameters.ContainsKey('HideGreen'))
611 | {
612 | if ('green', 'skipped' -notcontains $obj.Health) { $obj }
613 | }
614 | else { $obj }
615 | }
616 | }
617 | }
618 | }
619 | else
620 | {
621 | Write-Verbose "The [$($VsanCluster.Name)] cluster is not VSAN Enabled"
622 | }
623 | }
624 | End { }
625 |
626 | } #EndFunction Invoke-VSANHealthCheck
627 |
628 | Function Get-VSANCapability
629 | {
630 |
631 | <#
632 | .SYNOPSIS
633 | Get vSAN capabilities.
634 | .DESCRIPTION
635 | This function retreives vSAN capabilities for VCenter/Cluster(s)/VMHost(s).
636 | .PARAMETER Cluster
637 | Specifies a Cluster object(s), returned by Get-Cluster cmdlet.
638 | .PARAMETER Capability
639 | Specifies capabilities to filter out.
640 | .EXAMPLE
641 | PS C:\> Get-Cluster |Get-VSANCapability
642 | .EXAMPLE
643 | PS C:\> Get-Cluster VSAN-Cluster |Get-VSANCapability
644 | .EXAMPLE
645 | PS C:\> Get-Cluster VSAN-Cluster |Get-VSANCapability -Capability allflash
646 | .EXAMPLE
647 | PS C:\> Get-Cluster |Get-VSANCapability allflash, stretchedcluster, encryption
648 | .NOTES
649 | Author :: Roman Gelman @rgelman75
650 | Requirement :: PowerCLI 6.5.1 | PowerShell 4.0 | VC 6.0U2
651 | Version 1.0 :: 18-Jul-2017 :: [Release] :: Publicly available
652 | .LINK
653 | https://ps1code.com
654 | #>
655 |
656 | [CmdletBinding()]
657 | Param (
658 | [Parameter(Mandatory, ValueFromPipeline)]
659 | [VMware.VimAutomation.ViCore.Types.V1.Inventory.Cluster]$Cluster
660 | ,
661 | [Parameter(Mandatory = $false, Position = 0)]
662 | [ValidateSet('throttleresync', 'allflash', 'upgrade', 'decomwhatif', 'objectidentities',
663 | 'clusterconfig', 'stretchedcluster', 'configassist', 'unicastmode', 'iscsitargets',
664 | 'capability', 'witnessmanagement', 'cloudhealth', 'firmwareupdate', 'encryption',
665 | 'nestedfd', 'dataefficiency', 'perfsvcverbosemode', 'vumintegration', IgnoreCase = $false)]
666 | [string[]]$Capability
667 | )
668 |
669 | Begin
670 | {
671 | $WarningPreference = 'SilentlyContinue'
672 |
673 | $vccs = Get-VsanView -Id 'VsanCapabilitySystem-vsan-vc-capability-system' -Verbose:$false
674 |
675 | $VsanCapabilityDef = @{
676 | 'throttleresync' = 'Throttle Resync Traffic';
677 | 'allflash' = 'All-Flash Support';
678 | 'upgrade' = 'Upgrade';
679 | 'decomwhatif' = 'Decommission WhatIf';
680 | 'objectidentities' = 'Object Identities';
681 | 'clusterconfig' = 'Cluster Config';
682 | 'stretchedcluster' = 'Stretched Cluster';
683 | 'configassist' = 'Configuration Assist';
684 | 'unicastmode' = 'Unicast Mode';
685 | 'iscsitargets' = 'iSCSI Targets';
686 | 'capability' = 'Capability';
687 | 'witnessmanagement' = 'Witness';
688 | 'cloudhealth' = 'Cloud Health Check';
689 | 'firmwareupdate' = 'Firmware Updates';
690 | 'encryption' = 'Datastore Level Encryption';
691 | 'nestedfd' = 'Nested Fault Domains';
692 | 'dataefficiency' = 'Data Efficiency';
693 | 'perfsvcverbosemode' = 'Performance Service Verbose Mode';
694 | 'vumintegration' = 'VUM Integration';
695 | }
696 |
697 | ### Filter out Capabilities ###
698 | $CapabilityFullName = @()
699 | $CapabilityFullName += if ($PSBoundParameters.ContainsKey('Capability')) { $Capability | % { $VsanCapabilityDef.$_ } }
700 |
701 | ### VC Capabilities ###
702 | $VcName = if ($global:DefaultVIServers.Length -eq 1) { $global:DefaultVIServers.Name } else { Throw "You are connected to more than one VC, please disconnect first" }
703 | $VcType = if ($global:DefaultVIServers.ExtensionData.Content.About.OsType -match '^linux') { 'VCSA' } else { 'VCenter' }
704 |
705 | $VcCapability = $vccs.VsanGetCapabilities($null).Capabilities
706 | foreach ($VcCPB in $VcCapability)
707 | {
708 | $objVC = [pscustomobject] @{
709 | VIObject = $VcName
710 | Type = $VcType
711 | Capability = if ($VsanCapabilityDef.ContainsKey($VcCPB)) { $VsanCapabilityDef.$VcCPB } else { $VcCPB }
712 | }
713 | if ($PSBoundParameters.ContainsKey('Capability'))
714 | {
715 | if ($CapabilityFullName -contains $objVC.Capability) { $objVC }
716 | }
717 | else { $objVC }
718 | }
719 | }
720 | Process
721 | {
722 | $ClusterCapability = $vccs.VsanGetCapabilities($Cluster.Id)
723 | $ClusterCPB, $VMHostCPB = ($ClusterCapability).Where({ $_.Target -match '^ClusterComputeResource' }, 'Split')
724 |
725 | ### Cluster Capabilities ###
726 | foreach ($ClCPB in $ClusterCPB.Capabilities)
727 | {
728 | $objCluster = [pscustomobject] @{
729 | VIObject = $Cluster.Name
730 | Type = if ($Cluster.VsanEnabled) { 'VSANCluster' } else { 'Cluster' }
731 | Capability = if ($VsanCapabilityDef.ContainsKey($ClCPB)) { $VsanCapabilityDef.$ClCPB } else { $ClCPB }
732 | }
733 | if ($PSBoundParameters.ContainsKey('Capability'))
734 | {
735 | if ($CapabilityFullName -contains $objCluster.Capability) { $objCluster }
736 | }
737 | else { $objCluster }
738 | }
739 |
740 | ### VMHost Capabilities ###
741 | foreach ($VMHost in $VMHostCPB)
742 | {
743 | $VMHostName = [regex]::Match((Get-View -Id $VMHost.Target).Name, '^(.+?)(\.|$)').Groups[1].Value
744 | foreach ($EsxCPB in $VMHostCPB.Capabilities)
745 | {
746 | $objVMHost = [pscustomobject] @{
747 | VIObject = $VMHostName
748 | Type = 'VMHost'
749 | Capability = if ($VsanCapabilityDef.ContainsKey($EsxCPB)) { $VsanCapabilityDef.$EsxCPB } else { $EsxCPB }
750 | }
751 | if ($PSBoundParameters.ContainsKey('Capability'))
752 | {
753 | if ($CapabilityFullName -contains $objVMHost.Capability) { $objVMHost }
754 | }
755 | else { $objVMHost }
756 | }
757 | }
758 | }
759 | End { }
760 |
761 | } #EndFunction Get-VSANCapability
762 |
763 | Function Get-VSANLimit
764 | {
765 |
766 | <#
767 | .SYNOPSIS
768 | Get a vSAN cluster limits.
769 | .DESCRIPTION
770 | This function utilizes vSAN Management API to retrieve
771 | the exact same information provided by the RVC "vsan.check_limits" command.
772 | .PARAMETER VsanCluster
773 | Specifies a vSAN Cluster object(s), returned by Get-Cluster cmdlet.
774 | .NOTES
775 | Idea :: William Lam @lamw
776 | Author :: Roman Gelman @rgelman75
777 | Requirement :: PowerCLI 6.5.1, VSAN 6.6
778 | Version 1.0 :: 17-Jan-2018 :: [Release] :: Publicly available
779 | .EXAMPLE
780 | PS C:\> Get-Cluster VSAN-Cluster | Get-VSANLimit
781 | .EXAMPLE
782 | PS C:\> Get-Cluster | Get-VSANLimit -Verbose | ft -au
783 | .LINK
784 | http://www.virtuallyghetto.com/2017/06/how-to-convert-vsan-rvc-commands-into-powercli-andor-other-vsphere-sdks.html
785 | #>
786 |
787 | [CmdletBinding()]
788 | [Alias("Get-VSANLimits")]
789 | Param (
790 | [Parameter(Mandatory, ValueFromPipeline)]
791 | [Alias("Cluster")]
792 | [VMware.VimAutomation.ViCore.Types.V1.Inventory.Cluster]$VsanCluster
793 | )
794 |
795 | Begin
796 | {
797 | $FunctionName = '{0}' -f $MyInvocation.MyCommand
798 | Write-Verbose "$FunctionName :: Started at [$(Get-Date)]"
799 | }
800 | Process
801 | {
802 | if ($VsanCluster.VsanEnabled)
803 | {
804 | $VMHosts = ($VsanCluster | Get-VMHost -Verbose:$false | Sort-Object -Property Name)
805 |
806 | foreach ($VMHost in $VMHosts)
807 | {
808 | $connectionState = $VMHost.ExtensionData.Runtime.ConnectionState
809 | $vsanEnabled = (Get-View $VMHost.ExtensionData.ConfigManager.VsanSystem -Verbose:$false).Config.Enabled
810 |
811 | if ($connectionState -eq "connected" -and $vsanEnabled)
812 | {
813 | $vsanInternalSystem = Get-View $VMHost.ExtensionData.ConfigManager.VsanInternalSystem -Verbose:$false
814 |
815 | # Fetch RDT Information
816 | $jsonRdtLsomDom = $vsanInternalSystem.QueryVsanStatistics(@('rdtglobal', 'lsom-node', 'lsom', 'dom', 'dom-objects-counts')) | ConvertFrom-Json
817 |
818 | # Process RDT Data Start #
819 | $rdtAssocs = $jsonRdtLsomDom.'rdt.globalinfo'.assocCount.ToString() + "/" + $jsonRdtLsomDom.'rdt.globalinfo'.maxAssocCount.ToString()
820 | $rdtSockets = $jsonRdtLsomDom.'rdt.globalinfo'.socketCount.ToString() + "/" + $jsonRdtLsomDom.'rdt.globalinfo'.maxSocketCount.ToString()
821 | $rdtClients = 0
822 | foreach ($line in $jsonRdtLsomDom.'dom.clients' | Get-Member)
823 | {
824 | # crappy way to iterate through keys ...
825 | if ($($line.Name) -ne "Equals" -and $($line.Name) -ne "GetHashCode" -and $($line.Name) -ne "GetType" -and $($line.Name) -ne "ToString")
826 | {
827 | $rdtClients++
828 | }
829 | }
830 | $rdtOwners = 0
831 | foreach ($line in $jsonRdtLsomDom.'dom.owners.count' | Get-Member)
832 | {
833 | # crappy way to iterate through keys ...
834 | if ($($line.Name) -ne "Equals" -and $($line.Name) -ne "GetHashCode" -and $($line.Name) -ne "GetType" -and $($line.Name) -ne "ToString")
835 | {
836 | $rdtOwners++
837 | }
838 | }
839 | # Process RDT Data End #
840 |
841 | # Fetch Component information
842 | $jsonComponents = $vsanInternalSystem.QueryPhysicalVsanDisks(@('lsom_objects_count', 'uuid', 'isSsd', 'capacity', 'capacityUsed')) | ConvertFrom-Json
843 |
844 | # Process Component Data Start #
845 | $vsanUUIDs = @{ }
846 | $vsanDiskMgmtSystem = Get-VsanView -Id VimClusterVsanVcDiskManagementSystem-vsan-disk-management-system -Verbose:$false
847 | $diskGroups = $vsanDiskMgmtSystem.QueryDiskMappings($VMHost.ExtensionData.MoRef)
848 | foreach ($diskGroup in $diskGroups)
849 | {
850 | $mappings = $diskGroup.mapping
851 | foreach ($mapping in $mappings)
852 | {
853 | $ssds = $mapping.ssd
854 | $nonSsds = $mapping.nonSsd
855 |
856 | foreach ($ssd in $ssds)
857 | {
858 | $vsanUUIDs.add($ssd.vsanDiskInfo.vsanUuid, $ssd)
859 | }
860 |
861 | foreach ($nonSsd in $nonSsds)
862 | {
863 | $vsanUUIDs.add($nonSsd.vsanDiskInfo.vsanUuid, $nonSsd)
864 | }
865 | }
866 | }
867 | $maxComponents = $jsonRdtLsomDom.'lsom.node'.numMaxComponents
868 |
869 | $diskString = ""
870 | $hostComponents = 0
871 | foreach ($line in $jsonComponents | Get-Member)
872 | {
873 | # crappy way to iterate through keys ...
874 | if ($($line.Name) -ne "Equals" -and $($line.Name) -ne "GetHashCode" -and $($line.Name) -ne "GetType" -and $($line.Name) -ne "ToString")
875 | {
876 | if ($vsanUUIDs.ContainsKey($line.Name))
877 | {
878 | $numComponents = ($jsonRdtLsomDom.'lsom.disks'.$($line.Name).info.numComp).ToString()
879 | $maxCoponents = ($jsonRdtLsomDom.'lsom.disks'.$($line.Name).info.maxComp).ToString()
880 | $hostComponents += $jsonComponents.$($line.Name).lsom_objects_count
881 | $usage = ($jsonRdtLsomDom.'lsom.disks'.$($line.Name).info.capacityUsed * 100) / $jsonRdtLsomDom.'lsom.disks'.$($line.Name).info.capacity
882 | $usage = [Math]::ceiling($usage)
883 |
884 | $diskString += $vsanUUIDs.$($line.Name).CanonicalName + ": " + $usage + "% Components: " + $numComponents + "/" + $maxCoponents + "`n"
885 | }
886 | }
887 | }
888 | # Process Component Data End #
889 |
890 | [pscustomobject] @{
891 | Cluster = $VsanCluster.Name
892 | VMHost = $VMHost.Name
893 | RDT = "Assocs: " + $rdtAssocs + "`nSockets: " + $rdtSockets + "`nClients: " + $rdtClients + "`nOwners: " + $rdtOwners
894 | Disks = "Components: " + $hostComponents + "/" + $maxComponents + "`n" + $diskString
895 | }
896 | }
897 | }
898 | }
899 | else
900 | {
901 | Write-Verbose "$FunctionName :: The [$($VsanCluster.Name)] cluster is not VSAN Enabled"
902 | }
903 | }
904 | End { Write-Verbose "$FunctionName :: Finished at [$(Get-Date)]" }
905 |
906 | } #EndFunction Get-VSANLimit
907 |
908 | Function Get-VSANUsage
909 | {
910 |
911 | <#
912 | .SYNOPSIS
913 | Get VSAN Datastore usage.
914 | .DESCRIPTION
915 | This function retrieves VSAN Datastore usage for all or specific VM.
916 | .PARAMETER VsanCluster
917 | Specifies a vSAN Cluster object(s), returned by Get-Cluster cmdlet.
918 | .PARAMETER VM
919 | Specifies a VM name(s) or VM name pattern to query specifically.
920 | .PARAMETER Credential
921 | Specifies VMHost credentials for direct connect.
922 | .EXAMPLE
923 | PS C:\> Get-Cluster | Get-VSANUsage -Verbose | Format-Table -AutoSize
924 | Get VSAN datastore usage in all VSAN enabled clusters.
925 | .EXAMPLE
926 | PS C:\> Get-Cluster VSANCluster | Get-VSANUsage -VM vm1, vm2
927 | Get VSAN datastore usage for two particular VM.
928 | .EXAMPLE
929 | PS C:\> Get-Cluster VSANCluster | Get-VSANUsage -VM lnx*
930 | Get VSAN datastore usage for VM, taken by pattern.
931 | .NOTES
932 | Idea :: William Lam @lamw (Get-VSANVMDetailedUsage function)
933 | Author :: Roman Gelman @rgelman75
934 | Version 1.0 :: 19-Jul-2018 :: [Release] :: Publicly available
935 | .LINK
936 | https://www.virtuallyghetto.com/2018/06/retrieving-detailed-per-vm-space-utilization-on-vsan.html
937 | #>
938 |
939 | [CmdletBinding()]
940 | Param (
941 | [Parameter(Mandatory, ValueFromPipeline)]
942 | [Alias("Cluster")]
943 | [VMware.VimAutomation.ViCore.Types.V1.Inventory.Cluster]$VsanCluster
944 | ,
945 | [Parameter(Mandatory = $false)]
946 | [ValidateNotNullOrEmpty()]
947 | [string[]]$VM
948 | ,
949 | [Parameter(Mandatory = $false)]
950 | [pscredential]$Credential = $(Get-Credential -Message "VMHost Credentials")
951 | )
952 |
953 | Begin
954 | {
955 | $FunctionName = '{0}' -f $MyInvocation.MyCommand
956 | $rgxVsanPath = '\[(?vsanDatastore)\]\s(?.+)/(?.+)$'
957 | if ($PSBoundParameters.ContainsKey('VM')) { $VM = if ($VM -match '\*') { @((Get-VM $VM -Location $VsanCluster -Verbose:$false).Name) } else { $VM } }
958 | }
959 | Process
960 | {
961 | if ($VsanCluster.VsanEnabled)
962 | {
963 | $clusterView = Get-View -Verbose:$false -ViewType ClusterComputeResource -Property Name, Host -Filter @{ "name" = "$($VsanCluster.Name)" }
964 |
965 | foreach ($vmhost in $($clusterView.Host))
966 | {
967 | $vmhostView = Get-View $vmhost -Verbose:$false -Property Name
968 | $esxiConnection = Try { Connect-VIServer -Server $vmhostView.name -Credential $Credential -ErrorAction Stop }
969 | Catch { Write-Verbose "$FunctionName :: Failed to connect to the [$($VsanCluster.Name)\$($vmhostView.Name)] VMHost"; Break }
970 |
971 | $vos = Get-VSANView -Id "VsanObjectSystem-vsan-object-system" -Server $esxiConnection -Verbose:$false
972 | $identities = $vos.VsanQueryObjectIdentities($null, $null, $null, $false, $true, $true)
973 |
974 | $json = $identities.RawData | ConvertFrom-Json
975 | $jsonResults = $json.identities.vmIdentities
976 |
977 | foreach ($vmInstance in $jsonResults)
978 | {
979 | $identities = $vmInstance.objIdentities
980 | foreach ($identity in $identities | Sort-Object -Property "type", "description")
981 | {
982 | ### Retrieve the VM Name ###
983 | if ($identity.type -eq "namespace")
984 | {
985 | $vsanIntSys = Get-View (Get-VMHost -Server $esxiConnection -Verbose:$false).ExtensionData.ConfigManager.vsanInternalSystem -Verbose:$false
986 | $attributes = ($vsanIntSys.GetVsanObjExtAttrs($identity.uuid)) | ConvertFrom-JSON
987 |
988 | foreach ($attribute in $attributes | Get-Member)
989 | {
990 | if ("Equals", "GetHashCode", "GetType", "ToString" -notcontains $($attribute.Name))
991 | {
992 | $objectID = $attribute.name
993 | $vmName = $attributes.$($objectID).'User friendly name'
994 | }
995 | }
996 | }
997 |
998 | $VsanPath = [regex]::Match($identity.description, $rgxVsanPath)
999 |
1000 | $return = [pscustomobject] @{
1001 | Cluster = $VsanCluster.Name
1002 | VM = $vmName
1003 | Folder = $VsanPath.Groups['Folder'].Value
1004 | File = $VsanPath.Groups['File'].Value
1005 | Type = $identity.type
1006 | UsedGB = [Math]::Round($identity.physicalUsedB/1GB, 2)
1007 | ReservedGB = [Math]::Round($identity.reservedCapacityB/1GB, 2)
1008 | }
1009 |
1010 | ### Filter out a specific VM if provided ###
1011 | if ($PSBoundParameters.ContainsKey('VM')) { if ($VM -icontains $return.VM) { $return } }
1012 | else { $return }
1013 | }
1014 | }
1015 | Disconnect-VIServer -Server $esxiConnection -Confirm:$false -Force -Verbose:$false
1016 | }
1017 | }
1018 | else
1019 | {
1020 | Write-Verbose "$FunctionName :: The [$($VsanCluster.Name)] cluster is not VSAN Enabled"
1021 | }
1022 | }
1023 | End { }
1024 |
1025 | } #EndFunction Get-VSANUsage
1026 |
--------------------------------------------------------------------------------
/Vi-Module/Get-FunctionVersion.ps1:
--------------------------------------------------------------------------------
1 | Function Get-FunctionVersion
2 | {
3 |
4 | <#
5 | .SYNOPSIS
6 | Get PowerShell function version.
7 | .DESCRIPTION
8 | This function retrieves PowerShell function's current version or version history.
9 | .PARAMETER Function
10 | Specifies PowerShell function(s)/filter(s), returned by Get-Command cmdlet.
11 | .PARAMETER History
12 | If specified, a whole function version history returned.
13 | .EXAMPLE
14 | PS C:\> Get-Command Get-RDM |Get-FunctionVersion -History
15 | Get version history for a single function.
16 | .EXAMPLE
17 | PS C:\> Get-Command Get-RDM, Get-Version |Get-FunctionVersion
18 | .EXAMPLE
19 | PS C:\> Get-Command -Module Vi-Module |sort Name |Get-FunctionVersion |select * -exclude descr* |ft -au
20 | Get current version of all functions in a module.
21 | .EXAMPLE
22 | PS C:\> Get-Command -Module Vi-Module |Get-FunctionVersion -h |sort Function, Version |Format-Table -AutoSize
23 | .NOTES
24 | Author :: Roman Gelman @rgelman75
25 | Version 1.0 :: 16-Aug-2017 :: [Release] :: Publicly available
26 | Version 1.1 :: 19-Nov-2017 :: [Bugfix] :: Regex edited to prevent false positives while using a variable or cmdlet, containing 'Version' word in the function's code
27 | .LINK
28 | https://ps1code.com
29 | #>
30 |
31 | [CmdletBinding()]
32 | [Alias("fv")]
33 | [OutputType([PSCustomObject])]
34 | Param (
35 | [Parameter(Mandatory, ValueFromPipeline)]
36 | $Function
37 | ,
38 | [Parameter(Mandatory = $false)]
39 | [switch]$History
40 | )
41 |
42 | Begin
43 | {
44 | $ErrorActionPreference = 'Stop'
45 | $WarningPreference = 'SilentlyContinue'
46 | $rgxVersion = 'Version\s+.+\:{2}'
47 | $rgxVersionInfo = 'Version\s(?[\d|\.]+)\s+:{2}\s(?.+)\s:{2}\s(?.+)\s+:{2}\s(?.*$)'
48 | $Now = [datetime]::Now
49 | }
50 | Process
51 | {
52 | if ($Function -is [System.Management.Automation.FunctionInfo] -or $Function -is [System.Management.Automation.FilterInfo])
53 | {
54 | $VersionInfo = $Function.Definition.Split([System.Environment]::NewLine) | Select-String $rgxVersion
55 | $Versions = if ($PSBoundParameters.ContainsKey('History')) { $VersionInfo } else { $VersionInfo | select -Last 1 }
56 |
57 | foreach ($VersionLine in $Versions)
58 | {
59 | $VersionGroups = [regex]::Match($VersionLine.Line, $rgxVersionInfo)
60 | $Version = Try { [version]$VersionGroups.Groups['Version'].Value } Catch { [version]::New() }
61 | $Date = Try { [datetime]$VersionGroups.Groups['Date'].Value } Catch { [datetime]'30-Oct-1975' }
62 |
63 | [pscustomobject] @{
64 | Function = $Function.Name
65 | FunctionType = $Function.CommandType
66 | Version = $Version
67 | Published = $Date.Tostring('dd/MM/yyyy')
68 | DaysAgo = (New-TimeSpan -Start $Date -End $Now).Days
69 | Type = $VersionGroups.Groups['Info'].Value -replace ('\[', $null) -replace ('\]', $null)
70 | Description = $VersionGroups.Groups['Descr'].Value
71 | Module = $Function.ModuleName
72 | ModuleVersion = $Function.Version
73 | }
74 | }
75 | }
76 | }
77 | End
78 | {
79 |
80 | }
81 |
82 | } #EndFunction Get-FunctionVersion
83 |
--------------------------------------------------------------------------------
/Vi-Module/New-PercentageBar.ps1:
--------------------------------------------------------------------------------
1 | Function New-PercentageBar {
2 |
3 | <#
4 | .SYNOPSIS
5 | Create percentage bar.
6 | .DESCRIPTION
7 | This cmdlet creates percentage bar.
8 | .PARAMETER Percent
9 | Value in percents (%).
10 | .PARAMETER Value
11 | Value in arbitrary units.
12 | .PARAMETER MaxValue
13 | 100% value.
14 | .PARAMETER BarLength
15 | Bar length in chars.
16 | .PARAMETER BarView
17 | Different char sets to build the bar.
18 | .PARAMETER GreenBorder
19 | Percent value to change bar color from green to yellow (relevant with -DrawBar parameter only).
20 | .PARAMETER YellowBorder
21 | Percent value to change bar color from yellow to red (relevant with -DrawBar parameter only).
22 | .PARAMETER NoPercent
23 | Exclude percentage number from the bar.
24 | .PARAMETER DrawBar
25 | Directly draw the colored bar onto the PowerShell console (unsuitable for calculated properties).
26 | .EXAMPLE
27 | PS C:\> New-PercentageBar -Percent 90 -DrawBar
28 | Draw single bar with all default settings.
29 | .EXAMPLE
30 | PS C:\> New-PercentageBar -Percent 95 -DrawBar -GreenBorder 70 -YellowBorder 90
31 | Draw the bar and move the both color change borders.
32 | .EXAMPLE
33 | PS C:\> 85 |New-PercentageBar -DrawBar -NoPercent
34 | Pipeline the percent value to the function and exclude percent number from the bar.
35 | .EXAMPLE
36 | PS C:\> For ($i=0; $i -le 100; $i+=10) {New-PercentageBar -Percent $i -DrawBar -Length 100 -BarView AdvancedThin2; "`r"}
37 | Demonstrates advanced bar view with custom bar length and different percent values.
38 | .EXAMPLE
39 | PS C:\> $Folder = 'C:\reports\'
40 | PS C:\> $FolderSize = (Get-ChildItem -Path $Folder |measure -Property Length -Sum).Sum
41 | PS C:\> Get-ChildItem -Path $Folder -File |sort Length -Descending |select -First 10 |select Name,Length,@{N='SizeBar';E={New-PercentageBar -Value $_.Length -MaxValue $FolderSize}} |ft -au
42 | Get file size report and add calculated property 'SizeBar' that contains the percent of each file size from the folder size.
43 | .EXAMPLE
44 | PS C:\> $VolumeC = gwmi Win32_LogicalDisk |? {$_.DeviceID -eq 'c:'}
45 | PS C:\> Write-Host -NoNewline "Volume C Usage:" -ForegroundColor Yellow; `
46 | PS C:\> New-PercentageBar -Value ($VolumeC.Size-$VolumeC.Freespace) -MaxValue $VolumeC.Size -DrawBar; "`r"
47 | Get system volume usage report.
48 | .NOTES
49 | Author :: Roman Gelman.
50 | Version 1.0 :: 04-Jul-2016 :: Release.
51 | .LINK
52 | http://www.ps1code.com/single-post/2016/07/16/How-to-create-colored-and-adjustable-Percentage-Bar-in-PowerShell
53 | #>
54 |
55 | [CmdletBinding(DefaultParameterSetName='PERCENT')]
56 |
57 | Param (
58 | [Parameter(Mandatory,Position=1,ValueFromPipeline,ParameterSetName='PERCENT')]
59 | [ValidateRange(0,100)]
60 | [int]$Percent
61 | ,
62 | [Parameter(Mandatory,Position=1,ValueFromPipeline,ParameterSetName='VALUE')]
63 | [ValidateRange(0,[double]::MaxValue)]
64 | [double]$Value
65 | ,
66 | [Parameter(Mandatory,Position=2,ParameterSetName='VALUE')]
67 | [ValidateRange(1,[double]::MaxValue)]
68 | [double]$MaxValue
69 | ,
70 | [Parameter(Mandatory=$false,Position=3)]
71 | [Alias("BarSize","Length")]
72 | [ValidateRange(10,100)]
73 | [int]$BarLength = 20
74 | ,
75 | [Parameter(Mandatory=$false,Position=4)]
76 | [ValidateSet("SimpleThin","SimpleThick1","SimpleThick2","AdvancedThin1","AdvancedThin2","AdvancedThick")]
77 | [string]$BarView = "SimpleThin"
78 | ,
79 | [Parameter(Mandatory=$false,Position=5)]
80 | [ValidateRange(50,80)]
81 | [int]$GreenBorder = 60
82 | ,
83 | [Parameter(Mandatory=$false,Position=6)]
84 | [ValidateRange(80,90)]
85 | [int]$YellowBorder = 80
86 | ,
87 | [Parameter(Mandatory=$false)]
88 | [switch]$NoPercent
89 | ,
90 | [Parameter(Mandatory=$false)]
91 | [switch]$DrawBar
92 | )
93 |
94 | Begin {
95 |
96 | If ($PSBoundParameters.ContainsKey('VALUE')) {
97 |
98 | If ($Value -gt $MaxValue) {
99 | Throw "The [-Value] parameter cannot be greater than [-MaxValue]!"
100 | }
101 | Else {
102 | $Percent = $Value/$MaxValue*100 -as [int]
103 | }
104 | }
105 |
106 | If ($YellowBorder -le $GreenBorder) {Throw "The [-YellowBorder] value must be greater than [-GreenBorder]!"}
107 |
108 | Function Set-BarView ($View) {
109 | Switch -exact ($View) {
110 | "SimpleThin" {$GreenChar = [char]9632; $YellowChar = [char]9632; $RedChar = [char]9632; $EmptyChar = "-"; Break}
111 | "SimpleThick1" {$GreenChar = [char]9608; $YellowChar = [char]9608; $RedChar = [char]9608; $EmptyChar = "-"; Break}
112 | "SimpleThick2" {$GreenChar = [char]9612; $YellowChar = [char]9612; $RedChar = [char]9612; $EmptyChar = "-"; Break}
113 | "AdvancedThin1" {$GreenChar = [char]9632; $YellowChar = [char]9632; $RedChar = [char]9632; $EmptyChar = [char]9476; Break}
114 | "AdvancedThin2" {$GreenChar = [char]9642; $YellowChar = [char]9642; $RedChar = [char]9642; $EmptyChar = [char]9643; Break}
115 | "AdvancedThick" {$GreenChar = [char]9617; $YellowChar = [char]9618; $RedChar = [char]9619; $EmptyChar = [char]9482; Break}
116 | }
117 | $Properties = [ordered]@{
118 | Char1 = $GreenChar
119 | Char2 = $YellowChar
120 | Char3 = $RedChar
121 | Char4 = $EmptyChar
122 | }
123 | $Object = New-Object PSObject -Property $Properties
124 | $Object
125 | } #End Function Set-BarView
126 |
127 | $BarChars = Set-BarView -View $BarView
128 | $Bar = $null
129 |
130 | Function Draw-Bar {
131 |
132 | Param (
133 | [Parameter(Mandatory)][string]$Char
134 | ,
135 | [Parameter(Mandatory=$false)][string]$Color = 'White'
136 | ,
137 | [Parameter(Mandatory=$false)][boolean]$Draw
138 | )
139 |
140 | If ($Draw) {
141 | Write-Host -NoNewline -ForegroundColor ([System.ConsoleColor]$Color) $Char
142 | }
143 | Else {
144 | return $Char
145 | }
146 |
147 | } #End Function Draw-Bar
148 |
149 | } #End Begin
150 |
151 | Process {
152 |
153 | If ($NoPercent) {
154 | $Bar += Draw-Bar -Char "[ " -Draw $DrawBar
155 | }
156 | Else {
157 | If ($Percent -eq 100) {$Bar += Draw-Bar -Char "$Percent% [ " -Draw $DrawBar}
158 | ElseIf ($Percent -ge 10) {$Bar += Draw-Bar -Char " $Percent% [ " -Draw $DrawBar}
159 | Else {$Bar += Draw-Bar -Char " $Percent% [ " -Draw $DrawBar}
160 | }
161 |
162 | For ($i=1; $i -le ($BarValue = ([Math]::Round($Percent * $BarLength / 100))); $i++) {
163 |
164 | If ($i -le ($GreenBorder * $BarLength / 100)) {$Bar += Draw-Bar -Char ($BarChars.Char1) -Color 'DarkGreen' -Draw $DrawBar}
165 | ElseIf ($i -le ($YellowBorder * $BarLength / 100)) {$Bar += Draw-Bar -Char ($BarChars.Char2) -Color 'Yellow' -Draw $DrawBar}
166 | Else {$Bar += Draw-Bar -Char ($BarChars.Char3) -Color 'Red' -Draw $DrawBar}
167 | }
168 | For ($i=1; $i -le ($EmptyValue = $BarLength - $BarValue); $i++) {$Bar += Draw-Bar -Char ($BarChars.Char4) -Draw $DrawBar}
169 | $Bar += Draw-Bar -Char " ]" -Draw $DrawBar
170 |
171 | } #End Process
172 |
173 | End {
174 | If (!$DrawBar) {return $Bar}
175 | } #End End
176 |
177 | } #EndFunction New-PercentageBar
178 |
--------------------------------------------------------------------------------
/Vi-Module/README.md:
--------------------------------------------------------------------------------
1 | # $${\color{green}VMware \space VI \space Automation \space Module}$$
2 |
3 | > [!NOTE]
4 | > PowerShell `5` or above is required\
5 | > To check, type the following: `$PSVersionTable.PSVersion.Major`
6 |
7 | To install this module, drop the entire `Vi-Module` folder into one of your module directories
8 |
9 | The default PowerShell module paths are listed in the `$env:PSModulePath` environment variable
10 |
11 | To make it look better, split the paths in this manner: `$env:PSModulePath -split ';'`
12 |
13 | The default per-user module path is: `"$env:HOMEDRIVE$env:HOMEPATH\Documents\WindowsPowerShell\Modules"`
14 |
15 | The default computer-level module path is: `"$env:windir\System32\WindowsPowerShell\v1.0\Modules"`
16 |
17 | To use the module, type following command: `Import-Module Vi-Module -Force -Verbose`
18 |
19 | To see the commands imported, type `Get-Command -Module Vi-Module`
20 |
21 | For help on each individual cmdlet or function, run `Get-Help CmdletName -Full [-Online][-Examples]`
22 |
23 | |No|Cmdlet|Description|
24 | |----|----|----|
25 | |1|[Get-RDM](https://ps1code.com/2015/10/16/get-rdm-disks-powercli)|Get all VM with their RDM (Raw Device Mappings) disks|
26 | |2|[Convert-VmdkThin2EZThick](https://ps1code.com/2015/11/05/convert-vmdk-thin2thick-powercli)|Inflate thin virtual disks|
27 | |3|[Find-VcVm](https://cloud.githubusercontent.com/assets/6964549/17361776/d5dff80e-597a-11e6-85a2-a782db875f78.png)|Search VCenter VM throw direct connection to group of ESXi hosts. Thanks to VMGU.ru for the [article](http://www.vmgu.ru/news/vmware-vcenter-how-to-find-powered-off)|
28 | |4|[Set-PowerCLiTitle](https://ps1code.com/2015/11/17/set-powercli-title)|Write connected VI servers info to PowerCLi window title bar|
29 | |5|[Get-VMHostFirmwareVersion](https://ps1code.com/2016/01/09/esxi-bios-firmware-version-powercli)|Get a Firmware version and release date of your ESXi hosts|
30 | |6|[Compare-VMHostSoftwareVib](https://ps1code.com/2016/09/26/compare-esxi-powercli)|Deprecated. Use `Compare-VMHost -Compare VIB` instead|
31 | |7|[Get-VMHostBirthday](https://cloud.githubusercontent.com/assets/6964549/12399803/c8439dfa-be24-11e5-8141-09199caa301e.png)|Get ESXi hosts' installation date. Thanks to Magnus Andersson for his [idea](http://vcdx56.com/2016/01/05/find-esxi-installation-date/)|
32 | |8|[Enable-VMHostSSH/Disable-VMHostSSH](https://ps1code.com/2016/02/07/enable-disable-ssh-esxi-powercli)|Enable/Disable SSH on all ESXi hosts in a cluster|
33 | |9|[Set-VMHostNtpServer](https://ps1code.com/2016/03/10/set-esxi-ntp-powercli)|Set `NTP Servers` setting on ESXi hosts|
34 | |10|[Get-Version](https://ps1code.com/2016/05/25/get-version-powercli)|Get VMware Virtual Infrastructure objects' version info: `VM`, `ESXi Hosts`, `VDSwitches`, `Datastores`, `VCenters`, `PowerCLi`, `License Keys`|
35 | |11|[Compare-VMHost](https://ps1code.com/2016/09/26/compare-esxi-powercli)|Compare two or more ESXi hosts with PowerCLi|
36 | |12|[Move-Template2Datastore](https://ps1code.com/2016/12/19/migrate-vm-template-powercli)|Invoke Storage VMotion task for VM Template(s)|
37 | |13|[Connect-VMHostPutty](https://ps1code.com/2016/12/27/esxi-powershell-and-putty)|Connect to ESXi host(s) by putty SSH client with no password!|
38 | |14|[Set-MaxSnapshotNumber](https://ps1code.com/2017/01/24/max-snap-powercli)|Set maximum allowed VM snapshot number|
39 | |15|[Get-VMHostGPU](https://ps1code.com/2017/04/23/esxi-vgpu-powercli)|Get ESXi host(s) GPU info|
40 | |16|[Test-VMHotfix](https://ps1code.com/2017/05/23/test-vm-hotfix)|Test VM for installed Hotfix(es)|
41 | |17|[Test-VMPing](https://ps1code.com/2017/05/23/test-vm-hotfix)|Test VM accessibility|
42 | |18|[Search-Datastore](https://ps1code.com/2016/08/21/search-datastores-powercli)|Browse/Search VMware Datastores|
43 | |19|[Get-VMHostPnic/Get-VMHostHba](https://ps1code.com/2017/06/18/esxi-peripheral-devices-powercli)|Get ESXi hosts Peripheral devices|
44 | |20|[Set-SdrsCluster/Get-SdrsCluster](https://ps1code.com/2017/08/16/sdrs-powercli-part1)|Configure Storage DRS clusters|
45 | |21|[Add-SdrsAntiAffinityRule/Get-SdrsAntiAffinityRule/Remove-SdrsAntiAffinityRule](https://ps1code.com/2017/09/06/sdrs-powercli-part2)|Create and delete SDRS Anti-Affinity Rules|
46 | |22|[Invoke-SdrsRecommendation](https://ps1code.com/2017/09/06/sdrs-powercli-part2)|Run Storage DRS recommendations|
47 | |23|[Set-SdrsAntiAffinityRule](https://ps1code.com/2017/09/10/sdrs-powercli-part3)|Configure SDRS Anti-Affinity Rules|
48 | |24|[Convert-VI2PSCredential](https://ps1code.com/2017/09/18/vi2ps-cred-powercli)|Securely save and retrieve credentials|
49 | |25|[Get-VMGuestPartition/Expand-VMGuestPartition](https://ps1code.com/2017/10/17/extend-vm-guest-part-powercli)|Extend VM Guest Partition|
50 | |26|[Get-ViSession/Disconnect-ViSession](https://ps1code.com/2017/11/21/vcenter-sessions-powercli)|List and disconnect VCenter sessions|
51 | |27|[New-SmartSnapshot](https://ps1code.com/2017/11/26/vmware-smart-snapshot-powercli)|Intellectual VMware snapshots|
52 | |28|[Get-VMHostCDP](https://ps1code.com/2018/03/25/cdp-powercli)|Leverage Cisco Discovery Protocol|
53 | |29|[Get-VMLoggedOnUser](https://ps1code.com/2018/11/22/vm-logged-on-powercli)|Get VM Logged On users|
54 |
--------------------------------------------------------------------------------
/Vi-Module/Start-SleepProgress.ps1:
--------------------------------------------------------------------------------
1 | Function Start-SleepProgress {
2 |
3 | <#
4 | .SYNOPSIS
5 | Put a script in the sleep with progress bar.
6 | .DESCRIPTION
7 | The Start-SleepProgress cmdlet puts a script or cmdlet in the sleep for specified interval
8 | of either seconds/minutes/hours or until specified timestamp.
9 | .PARAMETER Second
10 | Seconds to sleep.
11 | .PARAMETER Minute
12 | Minutes to sleep.
13 | .PARAMETER Hour
14 | Hours to sleep.
15 | .PARAMETER Until
16 | Sleep until this date/time.
17 | .PARAMETER Force
18 | If desired timestamp specified by -Until parameter
19 | earlier than current time, then assume it will be tomorrow.
20 | .PARAMETER ScriptBlock
21 | Execute this code after the sleep is finished.
22 | Must be enclosed in the curly braces {}.
23 | .EXAMPLE
24 | C:\PS> Start-SleepProgress -Second 20
25 | .EXAMPLE
26 | C:\PS> Start-SleepProgress 10
27 | The default are seconds.
28 | .EXAMPLE
29 | C:\PS> Start-SleepProgress -Minutes 1.5
30 | Sleep ninety seconds.
31 | .EXAMPLE
32 | C:\PS> Start-SleepProgress -Hour 1.25
33 | Sleep one hour and fifteen minutes.
34 | .EXAMPLE
35 | C:\PS> Start-SleepProgress -Until (Get-Date -Hour 0 -Minute 0 -Second 0).AddDays(1) -ScriptBlock {(Get-Service).Where{$_.Status -eq 'Running'} > '.\services.txt'}
36 | Take snapshot of all running services and export the list to a text file at midnight.
37 | .EXAMPLE
38 | C:\PS> For ($i=0; $i -lt 10; $i++) {Start-SleepProgress -s 5 -ScriptBlock {(dir "$env:windir\Temp\" |sort LastWriteTime -Descending).Where({$_.Name -like '*.tmp'},'First')}}
39 | Every five seconds get the newest ".TMP" file from Windows temp directory. Do it ten times.
40 | .EXAMPLE
41 | C:\PS> Start-SleepProgress -Until 08:45
42 | Sleep until today 8:45 AM.
43 | .EXAMPLE
44 | C:\PS> Start-SleepProgress -Until 08:45 -Force
45 | Sleep until 8:45 AM. Maybe either today or tomorrow, it depends on the current time.
46 | .EXAMPLE
47 | C:\PS> Start-SleepProgress -Until 1:45PM
48 | Sleep until 13:45.
49 | .EXAMPLE
50 | C:\PS> Start-SleepProgress -Until (Get-Date -Hour 2 -Minute 0 -Second 0).AddDays(1)
51 | Sleep until tomorrow 2:00 AM.
52 | .NOTES
53 | Author :: Roman Gelman
54 | Version 1.0 :: 20-Nov-2016 :: [Release]
55 | The Start-SleepProgress cmdlet requires PowerShell 3.0
56 | Some examples that use the .Where() method require PowerShell 4.0 or later.
57 | The maximum sleep interval is twenty-four hours.
58 | .LINK
59 | http://www.ps1code.com/single-post/2016/11/20/Put-PowerShell-scripts-in-the-sleep-with-progress-bar
60 | #>
61 |
62 | [CmdletBinding(DefaultParameterSetName='SEC')]
63 |
64 | Param(
65 |
66 | [Parameter(Mandatory,Position=0,ParameterSetName='SEC')]
67 | [ValidateRange(1,86400)]
68 | [Alias("Seconds","s")]
69 | [uint32]$Second
70 | ,
71 | [Parameter(Mandatory,ParameterSetName='MIN')]
72 | [ValidateRange(1,1440)]
73 | [Alias("Minutes","m")]
74 | [decimal]$Minute
75 | ,
76 | [Parameter(Mandatory,ParameterSetName='HOUR')]
77 | [ValidateRange(1,24)]
78 | [Alias("Hours","h")]
79 | [decimal]$Hour
80 | ,
81 | [Parameter(Mandatory,ParameterSetName='TIME')]
82 | [datetime]$Until
83 | ,
84 | [Parameter(Mandatory=$false,ParameterSetName='TIME')]
85 | [switch]$Force
86 | ,
87 | [Parameter(Mandatory=$false)]
88 | [Alias("RunAfter")]
89 | [scriptblock]$ScriptBlock
90 | )
91 |
92 | Begin {
93 |
94 | Switch -exact ($PSCmdlet.ParameterSetName) {
95 |
96 | 'SEC' {
97 | $TimeSpan = New-TimeSpan -Start (Get-Date) -End (Get-Date).AddSeconds($Second)
98 | Break
99 | }
100 | 'MIN' {
101 | $Second = $Minute * 60 -as [uint32]
102 | $TimeSpan = New-TimeSpan -Start (Get-Date) -End (Get-Date).AddSeconds($Second)
103 | Break
104 | }
105 | 'HOUR' {
106 | $Second = $Hour * 3600 -as [uint32]
107 | $TimeSpan = New-TimeSpan -Start (Get-Date) -End (Get-Date).AddSeconds($Second)
108 | Break
109 | }
110 | 'TIME' {
111 | $TimeSpan = New-TimeSpan -Start ([datetime]::Now) -End $Until
112 | $TotalSecond = $TimeSpan.TotalSeconds
113 | If ($TotalSecond -le 0) {
114 | If ($Force) {Start-SleepProgress -Until $Until.AddDays(1)}
115 | Else {Throw "The timestamp [ $($Until.ToString()) ] is in the past!`nUse [-Force] parameter to shift the timestamp to tomorrow [ $($Until.AddDays(1)) ]."}
116 | } Else {
117 | $Second = $TotalSecond -as [uint32]
118 | }
119 | }
120 |
121 | } #EndSwitch
122 |
123 | $h = 'hour'
124 | $m = 'minute'
125 | $s = 'second'
126 |
127 | If ($TimeSpan.Hours -ne 1) {$h=$h+'s'}
128 | If ($TimeSpan.Minutes -ne 1) {$m=$m+'s'}
129 | If ($TimeSpan.Seconds -ne 1) {$s=$s+'s'}
130 |
131 | Function Add-LeadingZero {
132 | Param ([Parameter(Mandatory,Position=0)][int]$Digit)
133 | $str = $Digit.ToString()
134 | If ($str.Length -eq 1) {$str = '0'+$str}
135 | return $str
136 | } #EndFunction Add-LeadingZero
137 |
138 | } #EndBegin
139 |
140 | Process {
141 |
142 | If ($PSCmdlet.ParameterSetName -eq 'SEC') {
143 |
144 | For ($i=1; $i -le $Second; $i++) {
145 |
146 | Write-Progress -Activity "Waiting $($TimeSpan.Hours) $h $($TimeSpan.Minutes) $m and $($TimeSpan.Seconds) $s ..." `
147 | -CurrentOperation "Left time: $([int]($Second - $i)) seconds" `
148 | -Status "Elapsed time: $i seconds" -PercentComplete (100/$Second*$i)
149 | Start-Sleep -Milliseconds 980
150 | }
151 | } Else {
152 |
153 | For ($i=1; $i -le $Second; $i++) {
154 |
155 | $Now = Get-Date
156 | $TimeElapsed = New-TimeSpan -Start $Now -End $Now.AddSeconds($i)
157 | $TimeLeft = New-TimeSpan -Start $Now -End $Now.AddSeconds([int]($Second-$i))
158 | Write-Progress -Activity "Waiting $($TimeSpan.Hours) $h $($TimeSpan.Minutes) $m and $($TimeSpan.Seconds) $s ..." `
159 | -CurrentOperation "Left time: $(Add-LeadingZero $TimeLeft.Hours):$(Add-LeadingZero $TimeLeft.Minutes):$(Add-LeadingZero $TimeLeft.Seconds)" `
160 | -Status "Elapsed time: $(Add-LeadingZero $TimeElapsed.Hours):$(Add-LeadingZero $TimeElapsed.Minutes):$(Add-LeadingZero $TimeElapsed.Seconds)" `
161 | -PercentComplete (100/$Second*$i)
162 | Start-Sleep -Milliseconds 980
163 | }
164 | }
165 |
166 | Write-Progress -Activity "Completed" -Completed
167 |
168 | } #EndProcess
169 |
170 | End {
171 | If ($PSBoundParameters.ContainsKey('ScriptBlock')) {&$ScriptBlock}
172 | } #End
173 |
174 | } #EndFunction Start-SleepProgress
175 |
--------------------------------------------------------------------------------
/Vi-Module/Vi-Module.format.ps1xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 | Vi-Module_ViSession
9 | ViSession
10 |
11 |
12 | 12
13 | 32
14 | 26
15 | 22
16 | 22
17 | 11
18 |
19 |
20 |
21 |
22 | VC
23 | UserName
24 | Client
25 | LoginTime
26 | LastActiveTime
27 | IdleMinutes
28 |
29 |
30 |
31 |
32 |
33 |
36 |
37 | Vi-Module_ViCDP
38 | ViCDP
39 |
40 |
41 | 10Left
42 | 7Right
43 | 10Left
44 | 8Left
45 | 30Left
46 | 19Right
47 | 46Left
48 |
49 |
50 |
51 |
52 | VMHost
53 | NIC
54 | Vendor
55 | LinkMbps
56 | Switch
57 | PortId
58 | Vlan
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/Vi-Module/Vi-Module.psd1:
--------------------------------------------------------------------------------
1 | #
2 | # Module manifest for module 'Vi-Module'
3 | #
4 | # Generated by: Roman Gelman @rgelman75
5 | #
6 | # Generated on: 1/24/2019
7 | #
8 |
9 | @{
10 |
11 | # Script module or binary module file associated with this manifest.
12 | RootModule = 'Vi-Module'
13 |
14 | # Version number of this module.
15 | ModuleVersion = '1.4.8.0'
16 |
17 | # ID used to uniquely identify this module
18 | GUID = '6e9026b4-d063-4029-9869-19e79529d4b8'
19 |
20 | # Author of this module
21 | Author = 'Roman Gelman @rgelman75'
22 |
23 | # Company or vendor of this module
24 | CompanyName = 'Taldor Israel'
25 |
26 | # Copyright statement for this module
27 | Copyright = '(c) 2019 Roman Gelman @rgelman75. All rights reserved.'
28 |
29 | # Description of the functionality provided by this module
30 | Description = 'VMware VI Automation Module'
31 |
32 | # Minimum version of the Windows PowerShell engine required by this module
33 | PowerShellVersion = '5.0'
34 |
35 | # Name of the Windows PowerShell host required by this module
36 | # PowerShellHostName = ''
37 |
38 | # Minimum version of the Windows PowerShell host required by this module
39 | # PowerShellHostVersion = ''
40 |
41 | # Minimum version of Microsoft .NET Framework required by this module
42 | # DotNetFrameworkVersion = ''
43 |
44 | # Minimum version of the common language runtime (CLR) required by this module
45 | # CLRVersion = ''
46 |
47 | # Processor architecture (None, X86, Amd64) required by this module
48 | # ProcessorArchitecture = ''
49 |
50 | # Modules that must be imported into the global environment prior to importing this module
51 | # RequiredModules = @()
52 |
53 | # Assemblies that must be loaded prior to importing this module
54 | # RequiredAssemblies = @()
55 |
56 | # Script files (.ps1) that are run in the caller's environment prior to importing this module.
57 | ScriptsToProcess = 'New-PercentageBar.ps1', 'Write-Menu.ps1', 'Start-SleepProgress.ps1',
58 | 'Get-FunctionVersion.ps1', 'Vi-SDRS.ps1'
59 |
60 | # Type files (.ps1xml) to be loaded when importing this module
61 | # TypesToProcess = @()
62 |
63 | # Format files (.ps1xml) to be loaded when importing this module
64 | FormatsToProcess = 'Vi-Module.format.ps1xml'
65 |
66 | # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess
67 | # NestedModules = @()
68 |
69 | # Functions to export from this module
70 | FunctionsToExport = '*'
71 |
72 | # Cmdlets to export from this module
73 | CmdletsToExport = '*'
74 |
75 | # Variables to export from this module
76 | VariablesToExport = '*'
77 |
78 | # Aliases to export from this module
79 | AliasesToExport = '*'
80 |
81 | # DSC resources to export from this module
82 | # DscResourcesToExport = @()
83 |
84 | # List of all modules packaged with this module
85 | # ModuleList = @()
86 |
87 | # List of all files packaged with this module
88 | # FileList = @()
89 |
90 | # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell.
91 | PrivateData = @{
92 |
93 | PSData = @{
94 |
95 | # Tags applied to this module. These help with module discovery in online galleries.
96 | # Tags = @()
97 |
98 | # A URL to the license for this module.
99 | # LicenseUri = ''
100 |
101 | # A URL to the main website for this project.
102 | ProjectUri = 'http://www.ps1code.com/category/vmware-powercli/vi-module'
103 |
104 | # A URL to an icon representing this module.
105 | # IconUri = ''
106 |
107 | # ReleaseNotes of this module
108 | # ReleaseNotes = ''
109 |
110 | } # End of PSData hashtable
111 |
112 | } # End of PrivateData hashtable
113 |
114 | # HelpInfo URI of this module
115 | # HelpInfoURI = ''
116 |
117 | # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix.
118 | # DefaultCommandPrefix = ''
119 |
120 | }
121 |
122 |
--------------------------------------------------------------------------------
/Vi-Module/Vi-SDRS.ps1:
--------------------------------------------------------------------------------
1 | Class SdrsRule
2 | {
3 | [ValidateNotNullOrEmpty()][string]$DatastoreCluster
4 | [ValidateNotNullOrEmpty()][int]$RuleId
5 | [ValidateNotNullOrEmpty()][string]$RuleType
6 | [ValidateNotNullOrEmpty()][string]$RuleName
7 | [ValidateNotNullOrEmpty()][boolean]$Enabled
8 | [ValidateNotNullOrEmpty()][string[]]$VM
9 | [pscustomobject[]]$HardDisks
10 | }
11 |
12 | Function Get-SdrsCluster
13 | {
14 |
15 | <#
16 | .SYNOPSIS
17 | Get SDRS Cluster settings.
18 | .DESCRIPTION
19 | This function retrieves Storage DRS Cluster settings.
20 | .PARAMETER DatastoreCluster
21 | Specifies Datastore Cluster object(s), returned by Get-DatastoreCluster cmdlet.
22 | .PARAMETER VMOverrides
23 | If specified, only Virtual Machine overrided settings of SDRS cluster returned.
24 | .EXAMPLE
25 | PS C:\> Get-DatastoreCluster PROD |Get-SdrsCluster
26 | Get single SDRS cluster settings.
27 | .EXAMPLE
28 | PS C:\> Get-DatastoreCluster |Get-SdrsCluster |ft -au
29 | Get all available SDRS clusters' settings.
30 | .EXAMPLE
31 | PS C:\> Get-DatastoreCluster LAB* |Get-SdrsCluster -VMSettings |sort AutomationLevel |ft -au
32 | Get VMSettings for matched SDRS clusters.
33 | .EXAMPLE
34 | PS C:\> Get-DatastoreCluster DEV |Get-SdrsCluster -VMSettings |? {!$_.KeepVMDKsTogether}
35 | Get VMs allowed to distribute their HardDisks across Datastores within SRDS cluster.
36 | .NOTES
37 | Author :: Roman Gelman @rgelman75
38 | Shell :: Tested on PowerShell 5.0 | PowerCLi 6.5.1
39 | Platform :: Tested on vSphere 5.5 | VCenter 5.5U2
40 | Requirement :: PowerShell 3.0 | PowerCLi 5.0
41 | Version 1.0 :: 13-Aug-2017 :: [Release] :: Publicly available
42 | .LINK
43 | https://ps1code.com/2017/08/16/sdrs-powercli-part1
44 | #>
45 |
46 | [CmdletBinding()]
47 | [Alias("Get-ViMSdrsCluster")]
48 | [OutputType([PSCustomObject])]
49 | Param (
50 | [Parameter(Mandatory, ValueFromPipeline)]
51 | [VMware.VimAutomation.ViCore.Types.V1.DatastoreManagement.DatastoreCluster]$DatastoreCluster
52 | ,
53 | [Parameter(Mandatory = $false)]
54 | [Alias("VMSettings")]
55 | [switch]$VMOverrides
56 | )
57 |
58 | Begin
59 | {
60 | $ErrorActionPreference = 'Stop'
61 | $WarningPreference = 'SilentlyContinue'
62 | }
63 | Process
64 | {
65 | Try
66 | {
67 | $DscView = Get-View -VIObject $DatastoreCluster
68 | $DscViewConfig = $DscView.PodStorageDrsEntry.StorageDrsConfig.PodConfig
69 |
70 | ### Cluster Default AutomationLevel ###
71 | $Automation = switch ($DscViewConfig.DefaultVmBehavior)
72 | {
73 | 'automated' { 'Fully Automated'; Break }
74 | 'manual' { 'Manual Mode'; Break }
75 | default { $DscViewConfig.DefaultVmBehavior }
76 | }
77 |
78 | ### Cluster Default Intra-Vm Affinity ###
79 | $DefaultAffinity = if ($DscViewConfig.DefaultIntraVmAffinity) { 'KeepTogether' }
80 | else { 'DistributeAcrossDatastores' }
81 |
82 | if ($VMOverrides)
83 | {
84 | foreach ($VMConfig in $DscView.PodStorageDrsEntry.StorageDrsConfig.VmConfig)
85 | {
86 | $VmAutomation = switch ($VMConfig.Behavior)
87 | {
88 | 'automated' { 'Fully Automated'; Break }
89 | 'manual' { 'Manual Mode'; Break }
90 | '' { "Default ($Automation)" }
91 | default { $VMConfig.Behavior }
92 | }
93 |
94 | $Enabled = if ($VMConfig.IntraVmAntiAffinity) { $VMConfig.IntraVmAntiAffinity.Enabled }
95 | else { $null; if ($VMConfig.Enabled -eq $false) { $VmAutomation = 'Disabled' } }
96 |
97 | $Source = switch ($Enabled)
98 | {
99 | $true { 'Active Rule'; Break }
100 | $false { 'Disabled Rule'; Break }
101 | $null { 'Override' }
102 | }
103 |
104 | $IntraVmAffinity = if ($VMConfig.IntraVmAffinity -eq $null) { "Default ($($DscViewConfig.DefaultIntraVmAffinity))" }
105 | else { $VMConfig.IntraVmAffinity }
106 |
107 | $return = [pscustomobject] @{
108 | DatastoreCluster = $DscView.Name
109 | VM = (Get-View -Id $VMConfig.Vm).Name
110 | Source = $Source
111 | AutomationLevel = $VmAutomation
112 | KeepVMDKsTogether = $IntraVmAffinity
113 | }
114 | if ($return.AutomationLevel -imatch '^default' -and $return.KeepVMDKsTogether -imatch '^default') { } else { $return }
115 | }
116 | }
117 | else
118 | {
119 | ### AdvancedOptions ###
120 | $AdvOpt = if ($DscViewConfig.Option)
121 | {
122 | $Options = $DscViewConfig.Option.GetEnumerator() | % { [string]$_.Key + ' = ' + [string]$_.Value }
123 | $Options -join '; '
124 | }
125 | else
126 | {
127 | $null
128 | }
129 |
130 | ### Usage% ###
131 | $UsagePercent = [math]::Round(($DscView.Summary.Capacity - $DscView.Summary.FreeSpace)/$DscView.Summary.Capacity * 100, 0)
132 |
133 | ### CheckImbalanceEvery ###
134 | $CheckImbalancePeriod = $DscViewConfig.LoadBalanceInterval / 60
135 | $CheckImbalanceUnits = if ($CheckImbalancePeriod -eq 1)
136 | {
137 | 'Hour'
138 | }
139 | elseif (2 .. 23 -contains $CheckImbalancePeriod)
140 | {
141 | 'Hours'
142 | }
143 | else
144 | {
145 | 'Days'
146 | $CheckImbalancePeriod = [math]::Round($CheckImbalancePeriod / 24, 1)
147 | }
148 |
149 | [pscustomobject] @{
150 | DatastoreCluster = $DscView.Name
151 | CapacityTB = [math]::Round($DscView.Summary.Capacity/1TB, 1)
152 | FreeSpaceTB = [math]::Round($DscView.Summary.FreeSpace/1TB, 1)
153 | 'Usage%' = New-PercentageBar -Percent $UsagePercent
154 | TurnOnSDRS = $DscViewConfig.Enabled
155 | AutomationLevel = $Automation
156 | AdvancedOptions = $AdvOpt
157 | EnableIOMetric = $DscViewConfig.IoLoadBalanceEnabled
158 | 'UtilizedSpace%' = New-PercentageBar -Percent $DscViewConfig.SpaceLoadBalanceConfig.SpaceUtilizationThreshold
159 | IOLatency = "$($DscViewConfig.IoLoadBalanceConfig.IoLatencyThreshold)ms " + (New-PercentageBar -Percent $DscViewConfig.IoLoadBalanceConfig.IoLatencyThreshold -NoPercent)
160 | 'MinSpaceUtilizationDifference%' = New-PercentageBar -Percent $DscViewConfig.SpaceLoadBalanceConfig.MinSpaceUtilizationDifference
161 | CheckImbalanceEvery = "$CheckImbalancePeriod $CheckImbalanceUnits"
162 | IOImbalanceThreshold = "Aggressive " + (New-PercentageBar -Value $DscViewConfig.IoLoadBalanceConfig.IoLoadImbalanceThreshold -MaxValue 25 -NoPercent) + " Conservative"
163 | DefaultIntraVmAffinity = $DefaultAffinity
164 | }
165 | }
166 | }
167 | Catch
168 | {
169 | "{0}" -f $Error.Exception.Message
170 | }
171 | }
172 | End { }
173 |
174 | } #EndFunction Get-SdrsCluster
175 |
176 | Function Set-SdrsCluster
177 | {
178 |
179 | <#
180 | .SYNOPSIS
181 | Set SDRS Cluster settings.
182 | .DESCRIPTION
183 | This function configures Storage DRS Cluster.
184 | .PARAMETER DatastoreCluster
185 | Specifies Datastore Cluster object(s), returned by Get-DatastoreCluster cmdlet.
186 | .PARAMETER ShowBeforeState
187 | If specified, SDRS cluster state will be taken before applying changes.
188 | .PARAMETER DefaultIntraVmAffinity
189 | Specifies Default Intra-Vm Affinity policy (VMDK affinity) for SDRS Cluster.
190 | .PARAMETER TurnOnSRDS
191 | Enable/disable Storage DRS feature.
192 | .PARAMETER AutomationLevel
193 | Specifies SDRS Automation Level.
194 | .PARAMETER EnableIOMetric
195 | If $true will enable I/O Metric for SRDS recommendations.
196 | .PARAMETER UtilizedSpace
197 | Specifies SDRS Runtime Threshold on Utilized Space (%).
198 | .PARAMETER IOLatency
199 | Specifies SRDS Runtime Threshold on I/O Latency (ms).
200 | .PARAMETER MinSpaceUtilizationDifference
201 | Specifies utilization difference between source and destination until which no SDRS recommendations (%).
202 | .PARAMETER CheckImbalanceEveryMin
203 | Specifies how frequently to check imbalance (min).
204 | .PARAMETER IOImbalanceThreshold
205 | Specifies amount of imbalance that SDRS should tolerate.
206 | 1 - the most Aggressive (correct small imbalance), 25 - the most Conservative.
207 | .EXAMPLE
208 | PS C:\> Get-DatastoreCluster $DatastoreClusterName |Set-SdrsCluster
209 | Set Default Intra-Vm Affinity policy to DistributeAcrossDatastores on single DatastoreCluster.
210 | .EXAMPLE
211 | PS C:\> Get-DatastoreCluster -Location $DatacenterName |Set-SdrsCluster -TurnOnSDRS:$false
212 | Disable SDRS on all DatastoreClusters in a Datacenter.
213 | .EXAMPLE
214 | PS C:\> Get-DatastoreCluster |Set-SdrsCluster -AutomationLevel FullyAutomated -Confirm:$false
215 | Set Automation Level on all SRDS Clusters in Inventory.
216 | .EXAMPLE
217 | PS C:\> Get-DatastoreCluster $DatastoreClusterName |Set-SdrsCluster -EnableIOMetric:$true
218 | Enable I/O Metric for SRDS recommendations and set default Runtime Thresholds.
219 | .EXAMPLE
220 | PS C:\> Get-DatastoreCluster |Set-SdrsCluster -Option IgnoreAffinityRulesForMaintenance -Value 1
221 | Set SRDS Automation Advanced Option for all available SDRS Clusters.
222 | .NOTES
223 | Author :: Roman Gelman @rgelman75
224 | Shell :: Tested on PowerShell 5.0 | PowerCLi 6.5.1
225 | Platform :: Tested on vSphere 5.5 | VCenter 5.5U2
226 | Requirement :: PowerShell 3.0 | PowerCLi 5.0
227 | Version 1.0 :: 11-Jul-2017 :: [Release] :: Publicly available
228 | Version 1.1 :: 17-Aug-2017 :: [Bugfix] :: Alias renamed from Get-ViMSdrsCluster to Set-ViMSdrsCluster
229 | .LINK
230 | https://ps1code.com/2017/08/16/sdrs-powercli-part1
231 | #>
232 |
233 | [CmdletBinding(ConfirmImpact = 'High', SupportsShouldProcess, DefaultParameterSetName = 'VMAFFINITY')]
234 | [Alias("Set-ViMSdrsCluster")]
235 | [OutputType([PSCustomObject])]
236 | Param (
237 | [Parameter(Mandatory, ValueFromPipeline)]
238 | [VMware.VimAutomation.ViCore.Types.V1.DatastoreManagement.DatastoreCluster]$DatastoreCluster
239 | ,
240 | [Parameter(Mandatory = $false)]
241 | [switch]$ShowBeforeState
242 | ,
243 | [Parameter(Mandatory = $false, ParameterSetName = 'VMAFFINITY')]
244 | [ValidateSet("KeepTogether", "DistributeAcrossDatastores")]
245 | [string]$DefaultIntraVmAffinity = 'DistributeAcrossDatastores'
246 | ,
247 | [Parameter(Mandatory, ParameterSetName = 'SDRSONOFF')]
248 | [boolean]$TurnOnSDRS
249 | ,
250 | [Parameter(Mandatory, ParameterSetName = 'AUTOMATION')]
251 | [ValidateSet("FullyAutomated", "ManualMode")]
252 | [string]$AutomationLevel
253 | ,
254 | [Parameter(Mandatory, ParameterSetName = 'RUNTIMERULES')]
255 | [boolean]$EnableIOMetric
256 | ,
257 | [Parameter(Mandatory = $false, ParameterSetName = 'RUNTIMERULES')]
258 | [ValidateRange(50, 100)]
259 | [int]$UtilizedSpace = 80
260 | ,
261 | [Parameter(Mandatory = $false, ParameterSetName = 'RUNTIMERULES')]
262 | [ValidateRange(5, 100)]
263 | [int]$IOLatency = 15
264 | ,
265 | [Parameter(Mandatory = $false, ParameterSetName = 'RUNTIMERULES')]
266 | [ValidateRange(1, 50)]
267 | [int]$MinSpaceUtilizationDifference = 5
268 | ,
269 | [Parameter(Mandatory = $false, ParameterSetName = 'RUNTIMERULES')]
270 | [ValidateRange(60, 43200)]
271 | [int]$CheckImbalanceEveryMin = 480
272 | ,
273 | [Parameter(Mandatory = $false, ParameterSetName = 'RUNTIMERULES')]
274 | [ValidateRange(1, 25)]
275 | [int]$IOImbalanceThreshold = 5
276 | ,
277 | [Parameter(Mandatory, ParameterSetName = 'ADVOPT')]
278 | [string]$Option
279 | ,
280 | [Parameter(Mandatory, ParameterSetName = 'ADVOPT')]
281 | [string]$Value
282 | )
283 |
284 | Begin
285 | {
286 | $ErrorActionPreference = 'Stop'
287 | $WarningPreference = 'SilentlyContinue'
288 | $SRMan = Get-View StorageResourceManager
289 |
290 | switch ($PsCmdlet.ParameterSetName)
291 | {
292 | 'VMAFFINITY' { $Enabled = if ($DefaultIntraVmAffinity -eq 'KeepTogether') { $true } else { $false }; Break }
293 | 'AUTOMATION' { $Automation = if ($AutomationLevel -eq 'FullyAutomated') { 'automated' } else { 'manual' } }
294 | }
295 | }
296 | Process
297 | {
298 | if ($ShowBeforeState) { Get-SdrsCluster -DatastoreCluster $DatastoreCluster }
299 |
300 | $spec = New-Object VMware.Vim.StorageDrsConfigSpec
301 | $spec.PodConfigSpec = New-Object VMware.Vim.StorageDrsPodConfigSpec
302 |
303 | $ConfirmMsg = switch ($PsCmdlet.ParameterSetName)
304 | {
305 | 'VMAFFINITY'
306 | {
307 | "Set DefaultIntraVmAffinity to [$DefaultIntraVmAffinity]"
308 | $spec.PodConfigSpec.DefaultIntraVmAffinity = $Enabled
309 | Break
310 | }
311 | 'SDRSONOFF'
312 | {
313 | if ($TurnOnSDRS) { "Enable Storage DRS" }
314 | else { "Disable Storage DRS" }
315 | $spec.PodConfigSpec.Enabled = $TurnOnSDRS
316 | Break
317 | }
318 | 'AUTOMATION'
319 | {
320 | "Set Automation Level to [$AutomationLevel]"
321 | $spec.PodConfigSpec.DefaultVmBehavior = $Automation
322 | Break
323 | }
324 | 'RUNTIMERULES'
325 | {
326 | if ($EnableIOMetric) { "Enable I/O metric and Set Runtime Thresholds" }
327 | else { "Disable I/O metric and Set Runtime Thresholds" }
328 | $spec.PodConfigSpec.LoadBalanceInterval = $CheckImbalanceEveryMin
329 | $spec.PodConfigSpec.IoLoadBalanceConfig = New-Object VMware.Vim.StorageDrsIoLoadBalanceConfig
330 | $spec.PodConfigSpec.IoLoadBalanceEnabled = $EnableIOMetric
331 | $spec.PodConfigSpec.IoLoadBalanceConfig.IoLatencyThreshold = $IOLatency
332 | $spec.PodConfigSpec.IoLoadBalanceConfig.IoLoadImbalanceThreshold = $IOImbalanceThreshold
333 | $spec.PodConfigSpec.SpaceLoadBalanceConfig = New-Object VMware.Vim.StorageDrsSpaceLoadBalanceConfig
334 | $spec.PodConfigSpec.SpaceLoadBalanceConfig.SpaceUtilizationThreshold = $UtilizedSpace
335 | $spec.PodConfigSpec.SpaceLoadBalanceConfig.MinSpaceUtilizationDifference = $MinSpaceUtilizationDifference
336 | Break
337 | }
338 | 'ADVOPT'
339 | {
340 | "Set Advanced Option [$Option] to Value [$Value]"
341 | $opSpec = New-Object VMware.Vim.StorageDrsOptionSpec
342 | $opSpec.Option = New-Object VMware.Vim.OptionValue
343 | $opSpec.Option.Key = $Option
344 | $opSpec.Option.Value = $Value
345 | $spec.PodConfigSpec.Option = $opSpec
346 | }
347 | }
348 |
349 | if ($PSCmdlet.ShouldProcess("DatastoreCluster [$($DatastoreCluster.Name)]", $ConfirmMsg))
350 | {
351 | Try
352 | {
353 | $SRMan.ConfigureStorageDrsForPod($DatastoreCluster.Id, $spec, $true)
354 | Get-SdrsCluster -DatastoreCluster $DatastoreCluster
355 | }
356 | Catch
357 | {
358 | "{0}" -f $Error.Exception.Message
359 | }
360 | }
361 | }
362 | End { }
363 |
364 | } #EndFunction Set-SdrsCluster
365 |
366 | Function Add-SdrsAntiAffinityRule
367 | {
368 |
369 | <#
370 | .SYNOPSIS
371 | Create SDRS anti-affinity rules.
372 | .DESCRIPTION
373 | This function creates Storage DRS anti-affinity rules (VMDK and VM).
374 | .PARAMETER VM
375 | Specifies one virtual machine for which to create a VMDK SDRS (intra-VM) anti-affinity rule.
376 | .PARAMETER VMGroup
377 | Specifies two or more virtual machines for which to create a VM SDRS (inter-VM) anti-affinity rule.
378 | .PARAMETER DatastoreCLuster
379 | Specifies DatastoreCluster where the anti-affinity rule shall be created.
380 | .PARAMETER RuleName
381 | Specifies the rule name.
382 | .PARAMETER Enabled
383 | If specified, the rule should be enabled immediately after creation.
384 | .EXAMPLE
385 | PS C:\> Add-SdrsAntiAffinityRule -VM vm1 -Harddisk 2,3 -DatastoreCluster PROD
386 | Add intra-VM rule for two VM data disks.
387 | .EXAMPLE
388 | PS C:\> Get-DatastoreCluster TEST |Add-SdrsAntiAffinityRule -VM vm1 -Enabled
389 | Add intra-VM rule for all VM disks.
390 | .EXAMPLE
391 | PS C:\> Get-DatastoreCluster LAB |Add-SdrsAntiAffinityRule -VMGroup VM1, VM2, VM3 -Rule SQL
392 | Add inter-VM rule for three VM.
393 | .EXAMPLE
394 | PS C:\> Get-DatastoreCluster PROD |Add-SdrsAntiAffinityRule -VMGroup (Get-VM ntp*) -Rule NetAppAV -Enabled
395 | Add and enable inter-VM rule.
396 | .NOTES
397 | Author :: Luc Dekens @LucD22 (Set-SdrsAntiAffinity - http://www.lucd.info/2013/01/21/automate-your-sdrs-anti-affinity-rules/)
398 | Edited :: Roman Gelman @rgelman75
399 | Shell :: Tested on PowerShell 5.0 | PowerCLi 6.5.2
400 | Platform :: Tested on vSphere 5.5 | VCenter 5.5U2
401 | Requirement :: PowerShell 5.0
402 | Version 1.0 :: 24-Aug-2017 :: [Release] :: Publicly available
403 | .LINK
404 | https://ps1code.com/2017/09/06/sdrs-powercli-part2
405 | #>
406 |
407 | [CmdletBinding(ConfirmImpact = 'High', SupportsShouldProcess)]
408 | [Alias("Add-ViMSdrsAntiAffinityRule", "New-SdrsAntiAffinityRule")]
409 | [OutputType([SdrsRule])]
410 | Param (
411 | [Parameter(Mandatory, ParameterSetName = 'VMDK')]
412 | [PSObject]$VM
413 | ,
414 | [Parameter(Mandatory = $false, ParameterSetName = 'VMDK')]
415 | [ValidateCount(2, 60)]
416 | [int[]]$Harddisk
417 | ,
418 | [Parameter(Mandatory, ParameterSetName = 'VM')]
419 | [ValidateCount(2, 64)]
420 | [PSObject[]]$VMGroup
421 | ,
422 | [Parameter(Mandatory, ValueFromPipeline)]
423 | [PSObject]$DatastoreCluster
424 | ,
425 | [Parameter(Mandatory = $false)]
426 | [string]$RuleName
427 | ,
428 | [Parameter(Mandatory = $false)]
429 | [switch]$Enabled
430 | ,
431 | [Parameter(Mandatory = $false)]
432 | [switch]$Apply
433 | )
434 |
435 | Begin
436 | {
437 | $storMgr = Get-View StorageResourceManager
438 | $spec = New-Object VMware.Vim.StorageDrsConfigSpec
439 | $disk = &{ if (!$Harddisk) { 1 .. 60 } else { $Harddisk } }
440 | }
441 | Process
442 | {
443 | if ($DatastoreCluster -is [string]) { $DatastoreCluster = Get-DatastoreCluster -Name $DatastoreCluster }
444 |
445 | switch ($PsCmdlet.ParameterSetName)
446 | {
447 | 'VM'
448 | {
449 | $VMGroup = $VMGroup |% { if ($_ -is [string]) { Get-VM -Name $_ } else { $_ } }
450 | if (!$RuleName) { Throw "Please supply Rule name by [-RuleName] parameter" }
451 | $RuleDetails = "Inter-VM Anti-Affinity Rule for $($VMGroup.Count) VM"
452 |
453 | if (!$spec.podConfigSpec) { $spec.podConfigSpec = New-Object VMware.Vim.StorageDrsPodConfigSpec }
454 | $rule = New-Object VMware.Vim.ClusterRuleSpec
455 | $rule.Operation = "add"
456 | $rule.Info = New-Object VMware.Vim.ClusterAntiAffinityRuleSpec
457 | $rule.Info.Enabled = $Enabled
458 | $rule.Info.Name = $RuleName
459 | $rule.Info.Vm = $VMGroup |% { $_.ExtensionData.MoRef }
460 | $spec.podConfigSpec.rule += $rule
461 | }
462 | 'VMDK'
463 | {
464 | if ($VM -is [string]) { $VM = Get-VM -Name $VM }
465 | $RuleName = if (!$RuleName) { $VM.Name } else { $RuleName }
466 |
467 | $vmSpec = New-Object VMware.Vim.StorageDrsVmConfigSpec
468 | $vmSpec.Operation = "add"
469 | $vmSpec.Info = New-Object VMware.Vim.StorageDrsVmConfigInfo
470 | $vmSpec.Info.Vm = $VM.ExtensionData.MoRef
471 | $vmSpec.Info.Enabled = $true
472 | $vmSpec.Info.IntraVmAffinity = $false
473 | $vmSpec.Info.IntraVmAntiAffinity = New-Object VMware.Vim.VirtualDiskAntiAffinityRuleSpec
474 | $vmSpec.Info.IntraVmAntiAffinity.Enabled = $Enabled
475 | $vmSpec.Info.IntraVmAntiAffinity.Name = $RuleName
476 | $vmSpec.Info.IntraVmAntiAffinity.DiskId = &{
477 | $VM.ExtensionData.Config.Hardware.Device |
478 | where {
479 | $_ -is [VMware.Vim.VirtualDisk] -and
480 | $disk -contains $_.DeviceInfo.Label.Split(' ')[2]
481 | } | select -ExpandProperty Key
482 | }
483 | if ($vmspec.Info.IntraVmAntiAffinity.DiskId.Count -ge 2) { $spec.vmConfigSpec += $vmSpec }
484 | $RuleDetails = "Intra-VM Anti-Affinity Rule for $($vmspec.Info.IntraVmAntiAffinity.DiskId.Count) HardDisks of VM [$($VM.Name)]"
485 | }
486 | }
487 |
488 | $Action = if ($Enabled) { 'Add and enable' } else { 'Add' }
489 | if ($PSCmdlet.ShouldProcess("DatastoreCluster [$($DatastoreCluster.Name)]", "$Action SDRS $RuleDetails"))
490 | {
491 | $storMgr.ConfigureStorageDrsForPod($DatastoreCluster.ExtensionData.MoRef, $spec, $true)
492 | Start-SleepProgress -Second 10
493 | Get-SdrsAntiAffinityRule -DatastoreCluster (Get-DatastoreCluster $DatastoreCluster.Name) | ? { $_.RuleName -eq $RuleName }
494 | if ($Apply) { $storMgr.RefreshStorageDrsRecommendation($DatastoreCluster.Id) }
495 | }
496 | }
497 | End { }
498 |
499 | } #EndFunction Add-SdrsAntiAffinityRule
500 |
501 | Function Get-SdrsAntiAffinityRule
502 | {
503 |
504 | <#
505 | .SYNOPSIS
506 | Get SDRS anti-affinity rules.
507 | .DESCRIPTION
508 | This function retrieves Storage DRS anti-affinity rules (VMDK and VM).
509 | .PARAMETER DatastoreCluster
510 | Specifies SDRS Cluster object(s), returned by Get-DatastoreCluster cmdlet.
511 | .PARAMETER RuleType
512 | If specified, only rules of particular type will be returned.
513 | .EXAMPLE
514 | PS C:\> Get-DatastoreCluster PROD |Get-SdrsAntiAffinityRule
515 | .EXAMPLE
516 | PS C:\> Get-DatastoreCluster LAB |Get-SdrsAntiAffinityRule -RuleType InterVM |? {!$_.Enabled}
517 | .EXAMPLE
518 | PS C:\> Get-DatastoreCluster TEST |Get-SdrsAntiAffinityRule VMDK |select *, @{N='HDD'; E={'[' + (($_ |select -expand HardDisks).HardDisk -join '] [') + ']'}}
519 | .EXAMPLE
520 | PS C:\> Get-DatastoreCluster TEST |Get-SdrsAntiAffinityRule VMDK |select *, @{N='HddIndex'; E={'[' + (($_ |select -expand HardDisks).Index -join '] [') + ']'}} |select * -exclude HardDisks
521 | .NOTES
522 | Author :: Roman Gelman @rgelman75
523 | Shell :: Tested on PowerShell 5.0 | PowerCLi 6.5.2
524 | Platform :: Tested on vSphere 5.5 | VCenter 5.5U2
525 | Requirement :: PowerShell 5.0
526 | Version 1.0 :: 24-Aug-2017 :: [Release] :: Publicly available
527 | .LINK
528 | https://ps1code.com/2017/09/06/sdrs-powercli-part2
529 | #>
530 |
531 | [CmdletBinding()]
532 | [Alias("Get-ViMSdrsAntiAffinityRule")]
533 | [OutputType([SdrsRule])]
534 | Param (
535 | [Parameter(Mandatory, ValueFromPipeline)]
536 | [VMware.VimAutomation.ViCore.Types.V1.DatastoreManagement.DatastoreCluster]$DatastoreCluster
537 | ,
538 | [Parameter(Mandatory = $false, Position = 0)]
539 | [ValidateSet("InterVM", "VMDK")]
540 | [string]$RuleType
541 | )
542 |
543 | Begin
544 | {
545 | $WarningPreference = 'SilentlyContinue'
546 | }
547 | Process
548 | {
549 | ### Inter-VM rules ###
550 | if ($RuleType -ne 'VMDK')
551 | {
552 | $DatastoreCluster.ExtensionData.PodStorageDrsEntry.StorageDrsConfig.PodConfig.Rule | ? { $_.Vm } | %{
553 |
554 | [SdrsRule] @{
555 | DatastoreCluster = $DatastoreCluster.Name
556 | RuleId = $_.Key
557 | RuleType = 'Inter-VM'
558 | RuleName = $_.Name
559 | Enabled = $_.Enabled
560 | VM = ($_.Vm | % { (Get-View -Id $_).Name } | sort)
561 | HardDisks = $null
562 | }
563 | }
564 | }
565 |
566 | ### Intra-VM rules ###
567 | if ($RuleType -ne 'InterVM')
568 | {
569 | foreach ($VmConfig in ($DatastoreCluster.ExtensionData.PodStorageDrsEntry.StorageDrsConfig.VmConfig | ? { $_.IntraVmAntiAffinity }))
570 | {
571 | $Hdd = @()
572 | (Get-View -Id $VmConfig.Vm).Config.Hardware.Device | ? { $_ -is [VMware.Vim.VirtualDisk] } | %{
573 | $Hdd += if ($VmConfig.IntraVmAntiAffinity.DiskId.Contains($_.Key))
574 | {
575 | [pscustomobject] @{
576 | HardDisk = $_.DeviceInfo.Label
577 | Index = [regex]::Match($_.DeviceInfo.Label, '\d+').Value
578 | DiskId = $_.Key
579 | CapacityGB = [Math]::Round($_.CapacityInBytes/1GB, 0)
580 | }
581 | }
582 | }
583 |
584 | [SdrsRule] @{
585 | DatastoreCluster = $DatastoreCluster.Name
586 | RuleId = $VmConfig.IntraVmAntiAffinity.Key
587 | RuleType = 'VMDK'
588 | RuleName = $VmConfig.IntraVmAntiAffinity.Name
589 | Enabled = $VmConfig.IntraVmAntiAffinity.Enabled
590 | VM = (Get-View -Id $VmConfig.Vm).Name
591 | HardDisks = $Hdd
592 | }
593 | }
594 | }
595 | }
596 | End { }
597 |
598 | } #EndFunction Get-SdrsAntiAffinityRule
599 |
600 | Function Remove-SdrsAntiAffinityRule
601 | {
602 |
603 | <#
604 | .SYNOPSIS
605 | Delete SDRS anti-affinity rules.
606 | .DESCRIPTION
607 | This function deletes Storage DRS anti-affinity rules (VMDK and VM).
608 | .PARAMETER SdrsRule
609 | Specifies SDRS rule, returned by Get-SdrsAntiAffinityRule function.
610 | .EXAMPLE
611 | PS C:\> Get-DatastoreCluster LAB |Get-SdrsAntiAffinityRule |Remove-SdrsAntiAffinityRule
612 | .EXAMPLE
613 | PS C:\> Get-DatastoreCluster |Get-SdrsAntiAffinityRule VMDK |? {!$_.Enabled} |Remove-SdrsAntiAffinityRule -Confirm:$false
614 | Delete all inactive intra-VM SDRS rules in all! SDRS clusters with no confirmation!
615 | .NOTES
616 | Author :: Roman Gelman @rgelman75
617 | Shell :: Tested on PowerShell 5.0 | PowerCLi 6.5.2
618 | Platform :: Tested on vSphere 5.5 | VCenter 5.5U2
619 | Requirement :: PowerShell 5.0
620 | Version 1.0 :: 24-Aug-2017 :: [Release] :: Publicly available
621 | .LINK
622 | https://ps1code.com/2017/09/06/sdrs-powercli-part2
623 | #>
624 |
625 | [CmdletBinding(ConfirmImpact = 'High', SupportsShouldProcess)]
626 | [Alias("Remove-ViMSdrsAntiAffinityRule")]
627 | [OutputType([SdrsRule])]
628 | Param (
629 | [Parameter(Mandatory, ValueFromPipeline)]
630 | [SdrsRule]$SdrsRule
631 | )
632 |
633 | Begin
634 | {
635 | $storMgr = Get-View StorageResourceManager
636 | $spec = New-Object VMware.Vim.StorageDrsConfigSpec
637 | }
638 | Process
639 | {
640 | $DatastoreCluster = Get-DatastoreCluster -Name $SdrsRule.DatastoreCluster
641 |
642 | if ($SdrsRule.RuleType -eq 'Inter-VM')
643 | {
644 | if (!$spec.PodConfigSpec) { $spec.PodConfigSpec = New-Object VMware.Vim.StorageDrsPodConfigSpec }
645 |
646 | $rule = New-Object VMware.Vim.ClusterRuleSpec
647 | $rule.Operation = "remove"
648 | $rule.RemoveKey = $SdrsRule.RuleId
649 | $rule.Info = New-Object VMware.Vim.ClusterAntiAffinityRuleSpec
650 | $rule.Info.Enabled = $SdrsRule.Enabled
651 | $rule.Info.Key = $SdrsRule.RuleId
652 | $rule.Info.Name = $SdrsRule.RuleName
653 | $rule.Info.Vm = $SdrsRule.VM | %{ (Get-VM $_).Id }
654 |
655 | $spec.PodConfigSpec.Rule += $rule
656 | }
657 | else
658 | {
659 | $vmSpec = New-Object VMware.Vim.StorageDrsVmConfigSpec
660 | $vmSpec.Operation = "remove"
661 | $vmSpec.RemoveKey = $SdrsRule.RuleId
662 | $vmSpec.Info = New-Object VMware.Vim.StorageDrsVmConfigInfo
663 | $vmSpec.Info.Vm = (Get-VM $SdrsRule.VM[0]).Id
664 | $vmSpec.Info.Enabled = $true
665 | $vmSpec.Info.IntraVmAffinity = $false
666 | $vmSpec.Info.IntraVmAntiAffinity = New-Object VMware.Vim.VirtualDiskAntiAffinityRuleSpec
667 | $vmSpec.Info.IntraVmAntiAffinity.Enabled = $SdrsRule.Enabled
668 | $vmSpec.Info.IntraVmAntiAffinity.Name = $SdrsRule.RuleName
669 | $vmSpec.Info.IntraVmAntiAffinity.Key = $SdrsRule.RuleId
670 | $vmSpec.Info.IntraVmAntiAffinity.DiskId = $SdrsRule.HardDisks.DiskId
671 |
672 | $spec.VmConfigSpec += $vmSpec
673 | }
674 |
675 | $RemoveRule = Get-SdrsAntiAffinityRule -DatastoreCluster $DatastoreCluster | ? { $_.RuleId -eq $SdrsRule.RuleId }
676 |
677 | if ($PSCmdlet.ShouldProcess("DatastoreCluster [$($DatastoreCluster.Name)]", "Remove $($RemoveRule.RuleType) SDRS Rule [$($RemoveRule.RuleName)]"))
678 | {
679 | $storMgr.ConfigureStorageDrsForPod($DatastoreCluster.ExtensionData.MoRef, $spec, $true)
680 | }
681 | }
682 | End
683 | {
684 | Start-SleepProgress -Second 10
685 | Get-SdrsAntiAffinityRule -DatastoreCluster (Get-DatastoreCluster $DatastoreCluster.Name)
686 | }
687 |
688 | } #EndFunction Remove-SdrsAntiAffinityRule
689 |
690 | Function Set-SdrsAntiAffinityRule
691 | {
692 |
693 | <#
694 | .SYNOPSIS
695 | Configure SDRS anti-affinity rules.
696 | .DESCRIPTION
697 | This function edits Storage DRS anti-affinity rule(s):
698 | add/remove VM(s) or VMDK(s), rename or enable/disable rules.
699 | .PARAMETER SdrsRule
700 | Specifies SDRS rule, returned by Get-SdrsAntiAffinityRule function.
701 | .EXAMPLE
702 | PS C:\> Get-DatastoreCluster LAB |Get-SdrsAntiAffinityRule InterVM |Set-SdrsAntiAffinityRule -VM vm3 -Action Add
703 | Add one VM to inter-VM rule.
704 | .EXAMPLE
705 | PS C:\> Get-DatastoreCluster TEST |Get-SdrsAntiAffinityRule InterVM |Set-SdrsAntiAffinityRule -VM (Get-VM 'vm1[19]') -NewName Rule1 -Enable:$true -Confirm:$false
706 | Add nine VM (named vm11 to vm19) to inter-VM rule with no confirmation, rename and enable the rule after that.
707 | .EXAMPLE
708 | PS C:\> Get-DatastoreCluster DEV |Get-SdrsAntiAffinityRule VMDK |Set-SdrsAntiAffinityRule -Enable:$true -HardDisk 2
709 | Add one HardDisk to intra-VM rule and enable the rule after that.
710 | .EXAMPLE
711 | PS C:\> Get-DatastoreCluster PROD |Get-SdrsAntiAffinityRule VMDK |Set-SdrsAntiAffinityRule -NewName Rule3 -Enable:$false -Action Remove -HardDisk (2..5 -ne 4)
712 | Remove HardDisks 2 to 5 excluding 4 from a VMDK rule, rename and disable the rule after that.
713 | .NOTES
714 | Author :: Roman Gelman @rgelman75
715 | Shell :: Tested on PowerShell 5.0 | PowerCLi 6.5.2
716 | Platform :: Tested on vSphere 5.5 | VCenter 5.5U2
717 | Requirement :: PowerShell 5.0
718 | Version 1.0 :: 09-Sep-2017 :: [Release] :: Publicly available
719 | .LINK
720 | https://ps1code.com/2017/09/10/sdrs-powercli-part3
721 | #>
722 |
723 | [CmdletBinding(ConfirmImpact = 'High', SupportsShouldProcess, DefaultParameterSetName = 'VMDK')]
724 | [Alias("Set-ViMSdrsAntiAffinityRule")]
725 | [OutputType([SdrsRule])]
726 | Param (
727 | [Parameter(Mandatory, ValueFromPipeline)]
728 | [SdrsRule]$SdrsRule
729 | ,
730 | [Parameter(Mandatory = $false)]
731 | [string]$NewName
732 | ,
733 | [Parameter(Mandatory = $false)]
734 | [bool]$Enable
735 | ,
736 | [Parameter(Mandatory, ParameterSetName = 'VM')]
737 | [PSObject[]]$VM
738 | ,
739 | [Parameter(Mandatory = $false, ParameterSetName = 'VMDK')]
740 | [ValidateRange(1, 60)]
741 | [uint16[]]$HardDisk = (1..60)
742 | ,
743 | [Parameter(Mandatory = $false)]
744 | [ValidateSet('Add', 'Remove')]
745 | [string]$Action = 'Add'
746 | )
747 |
748 | Begin
749 | {
750 | $storMgr = Get-View StorageResourceManager
751 | $spec = New-Object VMware.Vim.StorageDrsConfigSpec
752 | }
753 | Process
754 | {
755 | $DatastoreCluster = Get-DatastoreCluster -Name $SdrsRule.DatastoreCluster
756 | $EditRule = Get-SdrsAntiAffinityRule -DatastoreCluster $DatastoreCluster | ? { $_.RuleId -eq $SdrsRule.RuleId }
757 |
758 | if ($SdrsRule.RuleType -eq 'Inter-VM' -and $PSCmdlet.ParameterSetName -eq 'VM')
759 | {
760 | if ($PSCmdlet.ShouldProcess("DatastoreCluster [$($DatastoreCluster.Name)]", "Edit $($EditRule.RuleType) SDRS Rule [$($EditRule.RuleName)]"))
761 | {
762 | ### Regenerate VM Id members list ###
763 | $AlreadyVM = { $SdrsRule.VM |% { (Get-VM $_).Id } }.Invoke()
764 | $NewMoRef += foreach ($NewVM in $VM)
765 | {
766 | if ($NewVM -is [string]) { (Get-VM $NewVM -ea SilentlyContinue).Id }
767 | elseif ($NewVM -is [VMware.VimAutomation.ViCore.Types.V1.Inventory.VirtualMachine]) { $NewVM.Id }
768 | else { }
769 | }
770 | if ($Action -eq 'Add') { $NewMoRef |% { if ($AlreadyVM -notcontains $_) { $AlreadyVM.Add($_) } } }
771 | else { $NewMoRef |% { if ($AlreadyVM -contains $_) { $AlreadyVM.Remove($_) | Out-Null } } }
772 |
773 | if ($AlreadyVM.Count -lt 2) { Throw "Oops, [$($SdrsRule.RuleName)] - At least two VM members must remain in Inter-VM rule!" }
774 |
775 | if (!$spec.PodConfigSpec) { $spec.PodConfigSpec = New-Object VMware.Vim.StorageDrsPodConfigSpec }
776 | $rule = New-Object VMware.Vim.ClusterRuleSpec
777 | $rule.Operation = "edit"
778 | $rule.Info = New-Object VMware.Vim.ClusterAntiAffinityRuleSpec
779 | $rule.Info.Enabled = if ($PSBoundParameters.ContainsKey('Enable')) { $Enable } else { $SdrsRule.Enabled }
780 | $rule.Info.Key = $SdrsRule.RuleId
781 | $rule.Info.Name = if ($PSBoundParameters.ContainsKey('NewName')) { $NewName } else { $SdrsRule.RuleName }
782 | $rule.Info.Vm = $AlreadyVM
783 | $spec.PodConfigSpec.Rule = $rule
784 |
785 | $storMgr.ConfigureStorageDrsForPod($DatastoreCluster.ExtensionData.MoRef, $spec, $true)
786 | Start-SleepProgress -Second 10
787 | Get-SdrsAntiAffinityRule -DatastoreCluster (Get-DatastoreCluster $DatastoreCluster.Name) | ? { $_.RuleId -eq $SdrsRule.RuleId }
788 | }
789 | }
790 |
791 | if ($SdrsRule.RuleType -eq 'VMDK' -and $PSCmdlet.ParameterSetName -eq 'VMDK')
792 | {
793 | if ($PSCmdlet.ShouldProcess("DatastoreCluster [$($DatastoreCluster.Name)]", "Edit $($EditRule.RuleType) SDRS Rule [$($EditRule.RuleName)]"))
794 | {
795 | ### Get VM's Index-to-DiskId list ###
796 | $RuleVM = Get-VM $SdrsRule.VM[0]
797 | $HddVM = $RuleVM.ExtensionData.Config.Hardware.Device | ? { $_ -is [VMware.Vim.VirtualDisk] } |
798 | select @{ N = 'Index'; E = { [regex]::Match($_.DeviceInfo.Label, '\d+').Value } },
799 | @{ N = 'DiskId'; E = { $_ | select -expand Key } }
800 |
801 | ### Translate HardDisk Indexes to DiskIds ###
802 | $Id = @(); foreach ($Index in $HardDisk) { foreach ($Hdd in $HddVM) { if ($Index -eq $Hdd.Index) { $Id += $Hdd.DiskId } } }
803 |
804 | ### Renew DiskId list ###
805 | $AlreadyVmdk = { $SdrsRule.HardDisks.DiskId }.Invoke()
806 | if ($Action -eq 'Add') { $Id |% { if ($AlreadyVmdk -notcontains $_) { $AlreadyVmdk.Add($_) } } }
807 | else { $Id |% { if ($AlreadyVmdk -contains $_) { $AlreadyVmdk.Remove($_) | Out-Null } } }
808 |
809 | if ($AlreadyVmdk.Count -eq 0) { Throw "Oops, [$($SdrsRule.RuleName)] - there is no possible to remove ALL HardDisks from VMDK rule!" }
810 |
811 | $vmSpec = New-Object VMware.Vim.StorageDrsVmConfigSpec
812 | $vmSpec.Operation = "edit"
813 | $vmSpec.Info = New-Object VMware.Vim.StorageDrsVmConfigInfo
814 | $vmSpec.Info.Vm = $RuleVM.Id
815 | $vmSpec.Info.Enabled = $true
816 | $vmSpec.Info.IntraVmAffinity = $false
817 | $vmSpec.Info.IntraVmAntiAffinity = New-Object VMware.Vim.VirtualDiskAntiAffinityRuleSpec
818 | $vmSpec.Info.IntraVmAntiAffinity.Enabled = if ($PSBoundParameters.ContainsKey('Enable')) { $Enable } else { $SdrsRule.Enabled }
819 | $vmSpec.Info.IntraVmAntiAffinity.Name = if ($PSBoundParameters.ContainsKey('NewName')) { $NewName } else { $SdrsRule.RuleName }
820 | $vmSpec.Info.IntraVmAntiAffinity.Key = $SdrsRule.RuleId
821 | $vmSpec.Info.IntraVmAntiAffinity.DiskId = $AlreadyVmdk
822 | $spec.vmConfigSpec = $vmSpec
823 |
824 | $storMgr.ConfigureStorageDrsForPod($DatastoreCluster.ExtensionData.MoRef, $spec, $true)
825 | Start-SleepProgress -Second 10
826 | Get-SdrsAntiAffinityRule -DatastoreCluster (Get-DatastoreCluster $DatastoreCluster.Name) | ? { $_.RuleId -eq $SdrsRule.RuleId }
827 | }
828 | }
829 | }
830 | End { }
831 |
832 | } #EndFunction Set-SdrsAntiAffinityRule
833 |
834 | Function Invoke-SdrsRecommendation
835 | {
836 |
837 | <#
838 | .SYNOPSIS
839 | Run Storage DRS.
840 | .DESCRIPTION
841 | This function runs SDRS cluster recommendations.
842 | .PARAMETER DatastoreCluster
843 | Specifies Datastore Cluster object(s), returned by Get-DatastoreCluster cmdlet.
844 | .EXAMPLE
845 | PS C:\> Get-DatastoreCluster |Invoke-SdrsRecommendation -Confirm:$false
846 | .EXAMPLE
847 | PS C:\> Get-DatastoreCluster LAB |Invoke-SdrsRecommendation
848 | .NOTES
849 | Author :: Roman Gelman @rgelman75
850 | Shell :: Tested on PowerShell 5.0 | PowerCLi 6.5.2
851 | Platform :: Tested on vSphere 5.5 | VCenter 5.5U2
852 | Version 1.0 :: 30-Aug-2017 :: [Release] :: Publicly available
853 | .LINK
854 | https://ps1code.com/2017/09/06/sdrs-powercli-part2
855 | #>
856 |
857 | [CmdletBinding(ConfirmImpact = 'High', SupportsShouldProcess)]
858 | [Alias("Invoke-ViMSdrsRecommendation")]
859 | Param (
860 | [Parameter(Mandatory, ValueFromPipeline)]
861 | [VMware.VimAutomation.ViCore.Types.V1.DatastoreManagement.DatastoreCluster]$DatastoreCluster
862 | )
863 |
864 | Begin
865 | {
866 | $ErrorActionPreference = 'Stop'
867 | $WarningPreference = 'SilentlyContinue'
868 | $storMgr = Get-View StorageResourceManager
869 | }
870 | Process
871 | {
872 | Try
873 | {
874 | if ($PSCmdlet.ShouldProcess("SDRS Cluster [$($DatastoreCluster.Name)]", "Run Storage DRS recommendations"))
875 | {
876 | $storMgr.RefreshStorageDrsRecommendation($DatastoreCluster.Id)
877 | [pscustomobject] @{
878 | DatastoreCluster = $DatastoreCluster.Name
879 | LastAction = $DatastoreCluster.ExtensionData.PodStorageDrsEntry.ActionHistory.Time.ToLocalTime() | sort -Descending | select -First 1
880 | Refreshed = Get-Date
881 | }
882 | }
883 | }
884 | Catch
885 | {
886 | "{0}" -f $Error.Exception.Message
887 | }
888 | }
889 | End { }
890 |
891 | } #EndFunction Invoke-SdrsRecommendation
892 |
--------------------------------------------------------------------------------
/Vi-Module/Write-Menu.ps1:
--------------------------------------------------------------------------------
1 | Function Write-Menu
2 | {
3 |
4 | <#
5 | .SYNOPSIS
6 | Display custom menu in the PowerShell console.
7 | .DESCRIPTION
8 | The Write-Menu cmdlet creates numbered and colored menues
9 | in the PS console window and returns the choiced entry.
10 | .PARAMETER Menu
11 | Menu entries.
12 | .PARAMETER PropertyToShow
13 | If your menu entries are objects and not the strings
14 | this is property to show as entry.
15 | .PARAMETER Prompt
16 | User prompt at the end of the menu.
17 | .PARAMETER Header
18 | Menu title (optional).
19 | .PARAMETER Shift
20 | Quantity of keys to shift the menu items right.
21 | .PARAMETER TextColor
22 | Menu text color.
23 | .PARAMETER HeaderColor
24 | Menu title color.
25 | .PARAMETER AddExit
26 | Add 'Exit' as very last entry.
27 | .EXAMPLE
28 | PS C:\> Write-Menu -Menu "Open","Close","Save" -AddExit -Shift 1
29 | Simple manual menu with 'Exit' entry and 'one-tab' shift.
30 | .EXAMPLE
31 | PS C:\> Write-Menu -Menu (Get-ChildItem 'C:\Windows\') -Header "`t`t-- File list --`n" -Prompt 'Select any file'
32 | Folder content dynamic menu with the header and custom prompt.
33 | .EXAMPLE
34 | PS C:\> Write-Menu -Menu (Get-Service) -Header ":: Services list ::`n" -Prompt 'Select any service' -PropertyToShow DisplayName
35 | Display local services menu with custom property 'DisplayName'.
36 | .EXAMPLE
37 | PS C:\> Write-Menu -Menu (Get-Process |select *) -PropertyToShow ProcessName |fl
38 | Display full info about choicen process.
39 | .INPUTS
40 | Any type of data (object(s), string(s), number(s), etc).
41 | .OUTPUTS
42 | [The same type as input object] Single menu item.
43 | .NOTES
44 | Author :: Roman Gelman @rgelman75
45 | Version 1.0 :: 21-Apr-2016 :: [Release] :: Publicly available
46 | Version 1.1 :: 03-Nov-2016 :: [Change] :: Supports a single item as menu entry
47 | Version 1.2 :: 22-Jun-2017 :: [Change] :: Throws an error if property, specified by -PropertyToShow does not exist. Code optimization
48 | Version 1.3 :: 27-Sep-2017 :: [Bugfix] :: Fixed throwing an error while menu entries are numeric values
49 | .LINK
50 | https://ps1code.com/2016/04/21/write-menu-powershell
51 | #>
52 |
53 | [CmdletBinding()]
54 | [Alias("menu")]
55 | Param (
56 | [Parameter(Mandatory, Position = 0)]
57 | [Alias("MenuEntry", "List")]
58 | $Menu
59 | ,
60 | [Parameter(Mandatory = $false, Position = 1)]
61 | [string]$PropertyToShow = 'Name'
62 | ,
63 | [Parameter(Mandatory = $false, Position = 2)]
64 | [ValidateNotNullorEmpty()]
65 | [string]$Prompt = 'Pick a choice'
66 | ,
67 | [Parameter(Mandatory = $false, Position = 3)]
68 | [Alias("Title")]
69 | [string]$Header = ''
70 | ,
71 | [Parameter(Mandatory = $false, Position = 4)]
72 | [ValidateRange(0, 5)]
73 | [Alias("Tab", "MenuShift")]
74 | [int]$Shift = 0
75 | ,
76 | [Parameter(Mandatory = $false, Position = 5)]
77 | [Alias("Color", "MenuColor")]
78 | [System.ConsoleColor]$TextColor = 'White'
79 | ,
80 | [Parameter(Mandatory = $false, Position = 6)]
81 | [System.ConsoleColor]$HeaderColor = 'Yellow'
82 | ,
83 | [Parameter(Mandatory = $false)]
84 | [ValidateNotNullorEmpty()]
85 | [Alias("Exit", "AllowExit")]
86 | [switch]$AddExit
87 | )
88 |
89 | Begin
90 | {
91 | $ErrorActionPreference = 'Stop'
92 | if ($Menu -isnot [array]) { $Menu = @($Menu) }
93 | if ($Menu[0] -is [psobject] -and $Menu[0] -isnot [string])
94 | {
95 | if (!($Menu | Get-Member -MemberType Property, NoteProperty -Name $PropertyToShow)) { Throw "Property [$PropertyToShow] does not exist" }
96 | }
97 | $MaxLength = if ($AddExit) { 8 } else { 9 }
98 | $AddZero = if ($Menu.Length -gt $MaxLength) { $true } else { $false }
99 | [hashtable]$htMenu = @{}
100 | }
101 | Process
102 | {
103 | ### Write menu header ###
104 | if ($Header -ne '') { Write-Host $Header -ForegroundColor $HeaderColor }
105 |
106 | ### Create shift prefix ###
107 | if ($Shift -gt 0) { $Prefix = [string]"`t" * $Shift }
108 |
109 | ### Build menu hash table ###
110 | for ($i = 1; $i -le $Menu.Length; $i++)
111 | {
112 | $Key = if ($AddZero)
113 | {
114 | $lz = if ($AddExit) { ([string]($Menu.Length + 1)).Length - ([string]$i).Length } else { ([string]$Menu.Length).Length - ([string]$i).Length }
115 | "0"*$lz + "$i"
116 | }
117 | else
118 | {
119 | "$i"
120 | }
121 |
122 | $htMenu.Add($Key, $Menu[$i-1])
123 |
124 | if ($Menu[$i] -isnot 'string' -and ($Menu[$i - 1].$PropertyToShow))
125 | {
126 | Write-Host "$Prefix[$Key] $($Menu[$i-1].$PropertyToShow)" -ForegroundColor $TextColor
127 | }
128 | else
129 | {
130 | Write-Host "$Prefix[$Key] $($Menu[$i - 1])" -ForegroundColor $TextColor
131 | }
132 | }
133 |
134 | ### Add 'Exit' row ###
135 | if ($AddExit)
136 | {
137 | [string]$Key = $Menu.Length+1
138 | $htMenu.Add($Key, "Exit")
139 | Write-Host "$Prefix[$Key] Exit" -ForegroundColor $TextColor
140 | }
141 |
142 | ### Pick a choice ###
143 | Do {
144 | $Choice = Read-Host -Prompt $Prompt
145 | $KeyChoice = if ($AddZero)
146 | {
147 | $lz = if ($AddExit) { ([string]($Menu.Length + 1)).Length - $Choice.Length } else { ([string]$Menu.Length).Length - $Choice.Length }
148 | if ($lz -gt 0) { "0" * $lz + "$Choice" } else { $Choice }
149 | }
150 | else
151 | {
152 | $Choice
153 | }
154 | }
155 | Until ($htMenu.ContainsKey($KeyChoice))
156 | }
157 | End
158 | {
159 | return $htMenu.get_Item($KeyChoice)
160 | }
161 |
162 | } #EndFunction Write-Menu
163 |
--------------------------------------------------------------------------------