├── 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 | # ![nsx-256](https://user-images.githubusercontent.com/6964549/49496838-920a3180-f86f-11e8-8c02-c924493b87dc.png)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 | # ![powerclilogo](https://cloud.githubusercontent.com/assets/6964549/17082247/44e1392e-517f-11e6-9cbe-9efa0277deaa.png)$${\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 | # ![powercli](https://user-images.githubusercontent.com/6964549/49510073-dc030f80-f88f-11e8-9f9a-e1f0415c7ad2.png) 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 | # ![vami-256](https://user-images.githubusercontent.com/6964549/49511546-1ff81380-f894-11e8-9625-c6c4ce4e854d.png)$${\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 | # ![vsan-256](https://user-images.githubusercontent.com/6964549/49511294-6e58e280-f893-11e8-8f41-f024dd5e9d63.png)$${\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 | # ![powercli](https://user-images.githubusercontent.com/6964549/49510073-dc030f80-f88f-11e8-9f9a-e1f0415c7ad2.png)$${\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 | --------------------------------------------------------------------------------