├── README.md ├── LICENSE ├── Limits ├── function_get-vmdisklimit.ps1 ├── function_set-vmdisklimit.ps1 ├── function_get-vmlimits.ps1 └── setVMDisklimits_frompolicy.ps1 ├── SPBM └── replicate_storagepolicies.ps1 ├── ESXi └── check_3par-AluaRule.ps1 ├── nsx └── change_switchprofile_nsxt.ps1 ├── DRS ├── drs-sql-remove.ps1 └── drs-sql.ps1 └── vSAN └── twonode_vsan_setup.ps1 /README.md: -------------------------------------------------------------------------------- 1 | # ps 2 | 3 | ## A collection of Powershell scripts 4 | 5 | --- 6 | 7 | ### [ESXi/check_3par-AluaRule.ps1](ESXi/check_3par-AluaRule.ps1) 8 | This script checks ESXi hosts for the existence of a Custom 3PAR Rule. 9 | 10 | 11 | ### [DRS/drs-sql.ps1](DRS/drs-sql.ps1) 12 | This script automates creation and maintenance of DRS groups 13 | 14 | 15 | ### [DRS/drs-sql-remove.ps1](DRS/drs-sql-remove.ps1) 16 | This script automates removal of members of a DRS group 17 | 18 | 19 | ### [SPBM/replicate_storagepolicies.ps1](SPBM/replicate_storagepolicies.ps1) 20 | This script replicates Storage Policies from one vCenter to another 21 | 22 | ### [Limits/setVMDisklimits_frompolicy.ps1](Limits/setVMDisklimits_frompolicy.ps1) 23 | This script automates the process of setting disk iops on VMDKs 24 | 25 | ### [vSAN/twonode_vsan_setup.ps1](vSAN/twonode_vsan_setup.ps1) 26 | This script automates the setup of a two-node vSAN cluster with a witness appliance 27 | 28 | ### [NSX/change_switchprofile_nsxt.ps1](nsx/change_switchprofile_nsxt.ps1) 29 | This script will change profiles attached to a NSX-T switch 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 rumart 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 | 23 | -------------------------------------------------------------------------------- /Limits/function_get-vmdisklimit.ps1: -------------------------------------------------------------------------------- 1 | 2 | function Get-VMDiskLimit { 3 | <# 4 | .SYNOPSIS 5 | Function for getting limits on a single VMDK 6 | .DESCRIPTION 7 | The function will get the IOPS limit on a specific 8 | VMDK 9 | .LINK 10 | https://www.rudimartinsen.com/2018/06/29/working-with-disk-limits-in-powercli/ 11 | .NOTES 12 | Info 13 | Author : Rudi Martinsen / Intility AS 14 | Date : 24/02-2018 15 | Version : 0.1.1 16 | Revised : 24/02-2018 17 | Changelog: 18 | 0.1.1 -- Added VM name to output 19 | .PARAMETER Harddisk 20 | VMDK to get limits from 21 | .EXAMPLE 22 | Get-VMDiskLimit -Harddisk $disk 23 | Gets the limits on a VMDK 24 | #> 25 | [cmdletbinding()] 26 | param( 27 | [Parameter(Mandatory=$true,ValueFromPipeline=$true,ParameterSetName="vmdk")] 28 | [Alias("VMDK")] 29 | [VMware.VimAutomation.ViCore.Types.V1.VirtualDevice.FlatHardDisk] 30 | $Harddisk 31 | ) 32 | 33 | $Harddisk.Parent | Get-VMResourceConfiguration | Select-Object -ExpandProperty DiskResourceConfiguration | Where-Object {$_.key -eq $Harddisk.ExtensionData.key} | Select-Object @{l="VM";e={$Harddisk.Parent.Name}},@{l="Label";e={$Harddisk.Name}},@{l="IOPSLimit";e={$_.DiskLimitIOPerSecond}} 34 | 35 | } 36 | -------------------------------------------------------------------------------- /SPBM/replicate_storagepolicies.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Script for replicating Storage Policies to multiple vCenters 4 | .DESCRIPTION 5 | The script retrieves Storage policies based on the names you 6 | specifies and replicates them to multiple vCenter servers 7 | The first vCenter in the vCenters variable will be the "master" 8 | and the following vCenters will be the ones to replicate to 9 | .NOTES 10 | Author: Rudi Martinsen / Intility AS 11 | Created: 16/03-2018 12 | Version 0.1.0 13 | Revised: 14 | Changelog: 15 | #> 16 | $vCenters = "vcenter-1","vcenter-2","vcenter-3" 17 | 18 | Connect-VIServer $vCenters -NotDefault 19 | 20 | $policies = Get-SpbmStoragePolicy -Server $vCenters[0] | where {$_.Name -like "VMDK-*" -or $_.name -like "VM-*"} 21 | 22 | $tempLocation = "D:\temp\policies" 23 | if(!(Test-Path $tempLocation)){ 24 | New-Item -ItemType Directory -Path $tempLocation 25 | } 26 | 27 | foreach($policy in $policies){ 28 | $policyName = $policy.Name 29 | $description = $policy.Description 30 | 31 | $filename = "$tempLocation\$policyname.xml" 32 | 33 | $policy | Export-SpbmStoragePolicy -FilePath $filename 34 | 35 | for($i = 1;$i -le $vcenters.count;$i++){ 36 | 37 | if(!(Get-SpbmStoragePolicy -Name $policyName -Server $vcenters[$i] -ErrorAction SilentlyContinue)){ 38 | Import-SpbmStoragePolicy -FilePath $filename -Name $policyName -Server $vcenters[$i] -Description $Description 39 | } 40 | else{ 41 | Write-Warning "Policy already exists" 42 | } 43 | } 44 | } 45 | 46 | -------------------------------------------------------------------------------- /Limits/function_set-vmdisklimit.ps1: -------------------------------------------------------------------------------- 1 | 2 | function Set-VMDiskLimit { 3 | <# 4 | .SYNOPSIS 5 | Function for setting limits on a single VMDK 6 | .DESCRIPTION 7 | The function will get the IOPS limit on a specific 8 | VMDK 9 | .LINK 10 | https://www.rudimartinsen.com/2018/06/29/working-with-disk-limits-in-powercli/ 11 | .NOTES 12 | Info 13 | Author : Rudi Martinsen / Intility AS 14 | Date : 24/02-2018 15 | Version : 1.0.0 16 | Revised : 24/02-2018 17 | Changelog: 18 | .PARAMETER Harddisk 19 | VMDK to set limits on 20 | .PARAMETER IOPSLimit 21 | The IOPSLimit to set 22 | .EXAMPLE 23 | Set-VMDiskLimit -Harddisk $disk -IOPSLimit 500 24 | Sets the IOPS limit on the VMDK to 500 25 | #> 26 | [cmdletbinding()] 27 | param( 28 | [Parameter(Mandatory=$true,ValueFromPipeline=$true,ParameterSetName="vmdk")] 29 | [Alias("VMDK")] 30 | [VMware.VimAutomation.ViCore.Types.V1.VirtualDevice.FlatHardDisk] 31 | $Harddisk, 32 | [Parameter(Mandatory=$true)] 33 | [Alias("Limit")] 34 | [int] 35 | $IOPSLimit 36 | ) 37 | 38 | $result = Get-VMResourceConfiguration -VM $Harddisk.parent | Set-VMResourceConfiguration -Disk $Harddisk -DiskLimitIOPerSecond $IOPSLimit 39 | $result.DiskResourceConfiguration | Where-Object {$_.key -eq $Harddisk.ExtensionData.Key} #| Select-Object @{l="VM";e={$Harddisk.Parent.Name}},@{l="Label";e={$Harddisk.Name}},@{l="IOPSLimit";e={$_.DiskLimitIOPerSecond}} 40 | 41 | } -------------------------------------------------------------------------------- /Limits/function_get-vmlimits.ps1: -------------------------------------------------------------------------------- 1 | 2 | function Get-VMLimits{ 3 | <# 4 | .SYNOPSIS 5 | Function for retrieving the IO limits set on a VM 6 | .DESCRIPTION 7 | The function will retrieve the Throughput limit (IOPS) set on 8 | the disks of a VM. 9 | A connection to the vCenter instance where the VM is running is needed. 10 | .LINK 11 | https://www.rudimartinsen.com/2018/06/29/working-with-disk-limits-in-powercli/ 12 | .NOTES 13 | Info 14 | Author : Rudi Martinsen / Intility AS 15 | Date : 28/06-2018 16 | Version : 1.0.0 17 | Revised : 18 | Changelog : 19 | .PARAMETER Name 20 | Specifies the VM to retrieve limits for 21 | .EXAMPLE 22 | Get-VMLimits -Name vm001 23 | This will retrieve the IO limits set on the VM "vm001" and 24 | output to screen 25 | #> 26 | [CmdLetBinding()] 27 | param( 28 | [Parameter(Mandatory=$true,Position=0)] 29 | [Alias('VM')] 30 | [string] 31 | $Name 32 | ) 33 | 34 | $vmObj = Get-VM -Name $Name 35 | 36 | if(!$vmObj){ 37 | Write-Error "Couldn't connect to virtual machine $Computername" 38 | break 39 | } 40 | 41 | if ($vmObj.Count -gt 1) { 42 | Write-Verbose -Message "Multiple VMs found" 43 | } 44 | $outputTbl = @() 45 | foreach ($v in $vmObj){ 46 | 47 | $diskLimits = $v | Get-VMResourceConfiguration | Select-Object -ExpandProperty DiskResourceConfiguration 48 | $disks = $v | Get-HardDisk 49 | foreach($disk in $disks){ 50 | $diskLimit = $diskLimits | Where-Object {$_.key -eq $disk.ExtensionData.Key} 51 | $o = [pscustomobject]@{ 52 | VM = $v.Name 53 | Name = $disk.Name 54 | Key = $disk.ExtensionData.Key 55 | IOPSLimit = $diskLimit.DiskLimitIOPerSecond 56 | } 57 | $outputTbl += $o 58 | } 59 | 60 | }#end foreach VM 61 | 62 | return $outputTbl 63 | } -------------------------------------------------------------------------------- /ESXi/check_3par-AluaRule.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Script to check for the existence of a 3PAR ALUA rule on ESXi hosts 4 | .DESCRIPTION 5 | The script connects to a vCenter to get a list of ESXi hosts and then 6 | connects to each of these to create an ESXCLI object which is used 7 | to check for the existence of a custom 3PAR VMW_SATP_ALUA rule 8 | .COMPONENT 9 | The script requires the VMware PowerCLI module which can be installed from the PSGallery 10 | .NOTES 11 | Author: Rudi Martinsen / Intility AS 12 | Created: 02/05-2017 13 | Version: 1.0.1 14 | Revised: 03/05-2017 15 | .LINK 16 | #Add custom SATP claimrule for HP 3PAR to ESXi - vcloudnine.de 17 | https://www.vcloudnine.de/add-custom-satp-claimrule-for-hp-3par-to-vmware-esxi/ 18 | .LINK 19 | #Automating the 3PAR ESXi SATP Rule Creation - virtuallyhyper.com 20 | http://virtuallyhyper.com/2013/10/automating-3par-satp-rule-creation-powercli/ 21 | .PARAMETER VCenter 22 | VCenter to connet to 23 | .PARAMETER Cluster 24 | Cluster to get hosts from 25 | .PARAMETER RootPass 26 | Password of ESXi Root user 27 | #> 28 | param ($VCenter,$Cluster) 29 | 30 | $RootPass = (Read-Host -Prompt "Please type password of ESXi Root user" -AsSecureString) 31 | 32 | Connect-VIServer $VCenter 33 | 34 | if($Cluster){ 35 | $vmhosts = Get-Cluster $Cluster | Get-VMHost -Server $vcenter | Where-Object {$_.PowerState -eq "PoweredOn"} 36 | } 37 | else{ 38 | $vmhosts = Get-VMHost -Server $vcenter | Where-Object {$_.PowerState -eq "PoweredOn"} 39 | } 40 | Disconnect-VIServer $vcenter -Confirm:$false 41 | 42 | #$RootPass = "Password here" 43 | $Pass = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($RootPass)) 44 | 45 | $outTbl = @() 46 | 47 | foreach($vmhost in $vmhosts){ 48 | 49 | Connect-VIServer $vmhost -Username root -Password $Pass | Out-Null 50 | $esxcli = Get-EsxCli -V2 -VMHost $vmhost.Name 51 | $rule = $esxcli.storage.nmp.satp.rule.list.Invoke() | where {$_.description -like "*3par*"} 52 | Disconnect-VIServer $vmhost.Name -Confirm:$false 53 | 54 | $output = [PSCustomObject]@{ 55 | HostName = $vmhost.name; 56 | RuleName = $rule.Name; 57 | PSP = $rule.DefaultPSP; 58 | RR_IOPS = $rule.PSPOptions; 59 | ClaimOptions = $rule.ClaimOptions; 60 | } 61 | $outTbl += $output 62 | 63 | #Command for setting the rule, could be added if it not exists 64 | #$esxcli.storage.nmp.satp.rule.add($null,"tpgs_on","HP 3Par Custom Rule",$null,$null,$null,"VV",$null,"VMW_PSP_RR","iops=1","VMW_SATP_ALUA",$null,$null,"3PARdata") 65 | 66 | } 67 | 68 | $outTbl | Format-Table 69 | -------------------------------------------------------------------------------- /nsx/change_switchprofile_nsxt.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | A script for changing a profile on a NSX-T logical switch 4 | .DESCRIPTION 5 | The script will change the Switch profile of the given type 6 | on a NSX-T logical switch 7 | 8 | Note that the script will only work with switches that have the 9 | protection level set to NOT_PROTECTED 10 | 11 | The script is only tested against NSX-T 2.4. 12 | .LINK 13 | https://rudimartinsen.com/2020/04/13/change-nsx-t-switch-profiles-with-powercli 14 | .NOTES 15 | Author: Rudi Martinsen / Proact IT Norge AS 16 | Created: 12/4-2020 17 | Version: 0.1.0 18 | Changelog: 19 | .PARAMETER Network 20 | Name of the logical network / switch 21 | .PARAMETER ProfileType 22 | The type of switch profile 23 | .PARAMETER ProfileName 24 | The name of the new profile 25 | .PARAMETER NSXManager 26 | The NSX Manager to connecto to 27 | .PARAMETER Credential 28 | A credential object for authenticating with NSX manager 29 | #> 30 | ############## 31 | # Parameters # 32 | ############## 33 | param( 34 | $LogicalNetwork, 35 | [ValidateSet("SwitchSecuritySwitchingProfile","SpoofGuardSwitchingProfile","IpDiscoverySwitchingProfile","MacManagementSwitchingProfile","PortMirroringSwitchingProfile","QosSwitchingProfile")] 36 | $ProfileType, 37 | $ProfileName, 38 | $NSXManager, 39 | [PSCredential] 40 | $Credential 41 | ) 42 | #Skip SSL validation 43 | add-type @" 44 | using System.Net; 45 | using System.Security.Cryptography.X509Certificates; 46 | public class TrustAllCertsPolicy : ICertificatePolicy { 47 | public bool CheckValidationResult( 48 | ServicePoint srvPoint, X509Certificate certificate, 49 | WebRequest request, int certificateProblem) { 50 | return true; 51 | } 52 | } 53 | "@ 54 | [System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy 55 | $AllProtocols = [System.Net.SecurityProtocolType]'Ssl3,Tls,Tls11,Tls12' 56 | [System.Net.ServicePointManager]::SecurityProtocol = $AllProtocols 57 | 58 | #Connect to NSX mgr 59 | Connect-NsxtServer -Server $NSXManager -Credential $Credential 60 | 61 | #Get the new profile 62 | $nestProfile = (Get-NsxtService -Name "com.vmware.nsx.switching_profiles").list().results.Where({$_.resource_type -eq $ProfileType -and $_.display_name -eq $ProfileName}) 63 | 64 | if(!$nestProfile){ 65 | throw "No profile found!" 66 | } 67 | 68 | #Get the logical switch 69 | $sw = (Get-NsxtService "com.vmware.nsx.logical_switches").list().results.Where({$_.display_name -eq $LogicalNetwork})[0] 70 | if(!$sw){ 71 | throw "No logical switch found!" 72 | } 73 | 74 | #Get the current profiles and get index of profile type 75 | $swProfiles = $sw.switching_profile_ids 76 | $profIndex = 0; 77 | for($i=0;$i -le $swProfiles.count;$i++){ 78 | if($swProfiles[$i].key -eq $ProfileType){ 79 | $profIndex = $i 80 | break 81 | } 82 | } 83 | 84 | #Set new value 85 | $chgProfile = $swProfiles[$profIndex] 86 | $chgProfile.value = $nestProfile.id 87 | 88 | #Change existing switch 89 | $swProfiles[$profIndex] = $chgProfile 90 | $sw.switching_profile_ids = $swProfiles 91 | 92 | #Update switch 93 | (Get-NsxtService -Name "com.vmware.nsx.logical_switches").update($sw.id,$sw) 94 | -------------------------------------------------------------------------------- /DRS/drs-sql-remove.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | The script will remove Hosts and VMs from a DRS group if a tag is not found 4 | .DESCRIPTION 5 | The script connects to vCenter and traverses all clusters specified. 6 | It checks hosts and vms in a Host and a VM group and checks that these 7 | have the correct tag specified. 8 | If the tag is not found they are removed from the DRS group 9 | .NOTES 10 | Author: Rudi Martinsen / Intility AS 11 | Date : 05/09-2017 12 | Version : 1.0.0 13 | Revised : 06/09-2017 14 | Changelog: 15 | .LINK 16 | http://www.rudimartinsen.com/2017/09/18/more-drs-group-automation/ 17 | .PARAMETER VCenter 18 | The vCenter server to process VMs from 19 | .PARAMETER Logfile 20 | File path for a logfile to output to 21 | #> 22 | [CmdletBinding()] 23 | param($vcenter,$logfile) 24 | 25 | #Tag and Rule parameters 26 | $vmgroup = "SQL-VM" 27 | $hostgroup = "SQL-Host" 28 | $vmtag = "SQL-Lic" 29 | $hosttag = "SQL-Host" 30 | $ruleName = "SQL-Lic" 31 | $ruleEnable = $false 32 | 33 | 34 | Write-Output "Connecting to vCenter $vcenter" | Out-File $logfile -Append 35 | Connect-VIServer $vcenter | Out-Null 36 | 37 | $clusters = Get-Cluster -server $vcenter | where {$_.name -ne "RH25-METRO" -and $_.name -ne "RH25-CL2"} 38 | Write-Output "Found $($clusters.count) clusters" | Out-File $logfile -Append 39 | 40 | foreach($cluster in $clusters){ 41 | 42 | Write-Output "Processing cluster $($cluster)" | Out-File $logfile -Append 43 | 44 | $sqlHosts = @() 45 | $remHosts = @() 46 | $sqlVMs = @() 47 | $remVMs = @() 48 | 49 | ######### 50 | # Hosts # 51 | ######### 52 | #Retrieve DRS group and traverse all members 53 | $drsGroup = Get-DrsClusterGroup -Cluster $cluster -Type VMHostGroup -Name $hosttag -ErrorAction SilentlyContinue 54 | foreach($member in $drsgroup.Member){ 55 | #Initiate tag existence variable 56 | $hostTagExists = $false 57 | $hostTagAssigned = $member | Get-TagAssignment -Category "Host Attributes" #| Where-Object {$_.Tag -eq $hosttag} 58 | 59 | if($hostTagAssigned){ 60 | foreach($hosttagA in $hostTagAssigned){ 61 | if($hosttaga.Tag.Name -eq $hosttag){ 62 | #Tag found, the host should remain a memeber of the group 63 | $hostTagExists = $true 64 | } 65 | } 66 | } 67 | if(!$hostTagExists){ 68 | #Tag NOT found, the host should be removed the group 69 | $drsGroup | Set-DrsClusterGroup -VMHost $member -Remove #-WhatIf 70 | $remHosts += $member 71 | } 72 | } 73 | 74 | if($remHosts){ 75 | Write-Output "Removed the following Hosts:" | Out-File $logfile -Append 76 | Write-Output "$($remHosts)" | Out-File $logfile -Append 77 | } 78 | 79 | ######### 80 | # VMs # 81 | ######### 82 | #Retrieve DRS group and traverse all members 83 | $drsGroupVM = Get-DrsClusterGroup -Cluster $cluster -Type VMGroup -Name $vmtag -ErrorAction SilentlyContinue 84 | foreach($member in $drsGroupVM.Member){ 85 | #Initiate tag existence variable 86 | $vmTagExists = $false 87 | $vmTagAssigned = $member | Get-TagAssignment -Category "VM Attributes" #| Where-Object {$_.Tag -eq $hosttag} 88 | 89 | if($vmTagAssigned){ 90 | foreach($vmtagA in $vmTagAssigned){ 91 | if($vmtagA.Tag.Name -eq $vmtag){ 92 | #Tag found, the vm should remain a memeber of the group 93 | $vmTagExists = $true 94 | } 95 | } 96 | } 97 | if(!$vmTagExists){ 98 | #Tag NOT found, the vm should be removed the group 99 | $drsGroupVM | Set-DrsClusterGroup -VM $member -Remove #-WhatIf 100 | $remVMs += $member 101 | } 102 | } 103 | 104 | if($remVMs){ 105 | Write-Output "Removed the following VMs:" | Out-File $logfile -Append 106 | Write-Output "$($remVMs)" | Out-File $logfile -Append 107 | } 108 | } 109 | 110 | Disconnect-VIServer $vcenter -Confirm:$false -------------------------------------------------------------------------------- /DRS/drs-sql.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | The script will create DRS groups and rules for SQL tagged Hosts and VMs 4 | .DESCRIPTION 5 | The script connects to vCenter and traverses all clusters specified. 6 | It checks for Hosts in the cluster with a SQL tag and if found creates a 7 | DRS Host group with the hosts found. 8 | 9 | It checks each VM for the existence of a SQL tag and if found creates a 10 | DRS VM group with the vms found 11 | 12 | If host and vm groups are found or created the script will check for 13 | the existence of a DRS rule which specifies that the VM group should run 14 | its VMs on the Host group. 15 | It the rule is not found it will be created 16 | .NOTES 17 | Author: Rudi Martinsen / Intility AS 18 | Date : 05/09-2017 19 | Version : 1.1.2 20 | Revised : 28/03-2018 21 | Changelog: 22 | 1.1.2 -- Removed unused variables 23 | 1.1.1 -- Added link 24 | 1.1.0 -- Added functionality for writing to the Notes field on VMs 25 | .LINK 26 | http://www.rudimartinsen.com/2017/09/08/automating-drs-groups-with-powercli/ 27 | .PARAMETER VCenter 28 | The vCenter server to process VMs from 29 | .PARAMETER Logfile 30 | File path for a logfile to output to 31 | #> 32 | [CmdletBinding()] 33 | param($vcenter,$logfile) 34 | 35 | #Tag and Rule parameters 36 | $vmtag = "SQL-Lic" 37 | $hosttag = "SQL-Host" 38 | $ruleName = "SQL-Lic" 39 | $ruleEnable = $false 40 | 41 | 42 | Write-Output "Connecting to vCenter $vcenter" | Out-File $logfile -Append 43 | Connect-VIServer $vcenter | Out-Null 44 | 45 | $clusters = Get-Cluster -server $vcenter 46 | Write-Output "Found $($clusters.count) clusters" | Out-File $logfile -Append 47 | 48 | foreach($cluster in $clusters){ 49 | Write-Output "Processing cluster $($cluster)" | Out-File $logfile -Append 50 | $sqlHosts = @() 51 | $sqlVMs = @() 52 | 53 | #Initiate existence variables 54 | $vmTagExists = $false 55 | $hostGroupExists = $false 56 | $vmGroupExists = $false 57 | 58 | #Check if DRS groups exists 59 | $drsHostGroup = Get-DrsClusterGroup -Cluster $cluster -Type VMHostGroup -Name $hosttag -ErrorAction SilentlyContinue 60 | $drsVMGroup = Get-DrsClusterGroup -Cluster $cluster -Type VMGroup -Name $vmtag -ErrorAction SilentlyContinue 61 | if($drsHostGroup){ 62 | Write-Verbose "DRS group for SQL hosts found" 63 | $hostGroupExists = $true 64 | } 65 | if($drsVMGroup){ 66 | Write-Verbose "DRS group for SQL hosts found" 67 | $vmGroupExists = $true 68 | } 69 | 70 | ######### 71 | # Hosts # 72 | ######### 73 | #Retrieve Connected hosts and check if the tag exists 74 | $vmhosts = $cluster | Get-VMHost -State Connected 75 | foreach($vmhost in $vmhosts){ 76 | $hostTagAssigned = $vmhost | Get-TagAssignment -Category "Host Attributes" 77 | if($hostTagAssigned){ 78 | foreach($hosttagA in $hostTagAssigned){ 79 | if($hosttaga.Tag.Name -eq $hosttag){ 80 | $sqlHosts += $vmhost 81 | $hostTagExists = $true 82 | } 83 | } 84 | } 85 | } 86 | 87 | #Create SQL-Host Group if tag is found and group is not found 88 | if(!$hostGroupExists -and $hostTagExists -and $sqlHosts -ne $null){ 89 | #Create DRS group 90 | Write-Verbose "Creating DRS Host group" 91 | New-DrsClusterGroup -Name $hosttag -Cluster $cluster -VMHost $sqlHosts #-WhatIf 92 | $drsHostGroup = Get-DrsClusterGroup -Cluster $cluster -Type VMHostGroup -Name $hosttag -ErrorAction SilentlyContinue 93 | if($drsHostGroup){ 94 | $hostGroupExists = $true 95 | } 96 | } 97 | else{ 98 | #Write-Verbose "Host tag doesn't exist, no need for the group." 99 | } 100 | 101 | #Check if Hosts are a member of the DRS group 102 | if($hostGroupExists -and $hostTagExists){ 103 | foreach($sqlHost in $sqlHosts){ 104 | if(!$drsHostGroup.Member.Contains($sqlHost)){ 105 | #Host is NOT a member, adding 106 | $drsHostGroup | Set-DrsClusterGroup -Add -VMHost $sqlhost #-WhatIf 107 | } 108 | } 109 | } 110 | 111 | ######### 112 | # VMs # 113 | ######### 114 | #Retrieve vms and check if the tag exists 115 | $vms = $cluster | Get-VM 116 | foreach($vm in $vms){ 117 | $vmTagAssigned = $vm | Get-TagAssignment -Category "VM Attributes" 118 | if($vmTagAssigned){ 119 | foreach($vmtagA in $vmTagAssigned){ 120 | if($vmtagA.Tag.Name -eq $vmtag){ 121 | $sqlVMs += $vm 122 | $vmTagExists = $true 123 | if($vm.Notes -notlike "*sql*"){ 124 | if($vm.notes -eq $null -or $vm.notes -eq ""){ 125 | Set-VM $vm -Notes $vmtag -Confirm:$false 126 | } 127 | else{ 128 | $newnote = $vm.Notes + ";$vmtag" 129 | Set-VM -VM $vm -Notes $newnote -Confirm:$false 130 | } 131 | } 132 | } 133 | } 134 | } 135 | } 136 | 137 | #SQL VMs found but no SQL hosts found. Should this be reported? 138 | if($vmTagExists -and !$hostTagExists){ 139 | Write-Verbose "VMs with SQL tag exists, but there are no hosts with a SQL tag!" 140 | } 141 | 142 | #Create SQL VM Group if tag is found and group is not found 143 | if(!$vmGroupExists -and $vmTagExists -and $sqlHosts -ne $null){ 144 | Write-Verbose "Creating DRS VM group" 145 | New-DrsClusterGroup -Name $vmtag -Cluster $cluster -VM $sqlVMs #-WhatIf 146 | $drsVMGroup = Get-DrsClusterGroup -Cluster $cluster -Type VMGroup -Name $vmtag -ErrorAction SilentlyContinue 147 | if($drsVMGroup){ 148 | $vmGroupExists = $true 149 | } 150 | } 151 | else{ 152 | #Write-Verbose "VM tag doesn't exist, no need for the group." 153 | } 154 | 155 | #Check if VM are a member of the DRS group 156 | if($vmGroupExists -and $vmTagExists){ 157 | foreach($sqlVM in $sqlVMs){ 158 | if(!$drsVMGroup.Member.Contains($sqlVM)){ 159 | #VM is NOT a member, adding 160 | $drsVMGroup | Set-DrsClusterGroup -Add -VM $sqlVM #-WhatIf 161 | } 162 | } 163 | } 164 | 165 | 166 | ############ 167 | # DRS Rule # 168 | ############ 169 | #Check DRS Rule 170 | if($hostGroupExists -and $vmGroupExists){ 171 | $drsRules = Get-DrsVMHostRule -Cluster $cluster -Type ShouldRunOn -VMHostGroup $drsHostGroup -VMGroup $drsVMGroup 172 | if(!$drsRules){ 173 | #Rule doesn't exist 174 | Write-Verbose "Creating DRS rule" 175 | New-DrsVMHostRule -Name $ruleName -Cluster $cluster -VMHostGroup $drsHostGroup -VMGroup $drsVMGroup -Type MustRunOn -Enabled:$ruleEnable #-WhatIf 176 | } 177 | } 178 | 179 | } 180 | Disconnect-VIServer $vcenter -Confirm:$false -------------------------------------------------------------------------------- /Limits/setVMDisklimits_frompolicy.ps1: -------------------------------------------------------------------------------- 1 | param($VCenter,$Cluster,$LogFile) 2 | <# 3 | .SYNOPSIS 4 | The script will set VM disk limits based on the configured Storage Policy 5 | .DESCRIPTION 6 | The script checks and sets VM disk limits on all VMs from the Storage Policies 7 | configured on the disk. 8 | 9 | VMs are filtered against a list of VMs that should NOT be touched 10 | .LINK 11 | https://www.rudimartinsen.com/2018/07/02/automating-disk-limits-in-vsphere/ 12 | .NOTES 13 | Author: Rudi Martinsen / Intility AS 14 | Date : 29/06-2018 15 | Version : 1.0.0 16 | Revised : 17 | Changelog: 18 | .PARAMETER VCenter 19 | The vCenter server to process VMs from 20 | .PARAMETER Cluster 21 | The cluster to process VMs from 22 | .PARAMETER LogFile 23 | The logfile to write script output to 24 | #> 25 | ############# 26 | # Functions # 27 | ############# 28 | function Write-Log { 29 | <# 30 | .SYNOPSIS 31 | The function enables scripts to write to a logfile 32 | .DESCRIPTION 33 | The function accepts two parameters, logfile and message 34 | Logfile parameter defaults to c:\temp\logfile.log if not passed 35 | The function will check if the logfile already exists and will append if found 36 | .NOTES 37 | Info 38 | Author : Rudi Martinsen -- rudi.martinsen@gmail.com 39 | Date : 08/05-2012 40 | Version : 3 41 | Revised : 05/02-2013 42 | .PARAMETER logfile 43 | The path to logfile 44 | Defaults to c:\temp\logfile.log if not passed 45 | .PARAMETER message 46 | The message to be written to the logfile. Current date&time is 47 | included in the message by default 48 | .EXAMPLE 49 | Write-Log -logfile c:\script\logfile.txt -message "Message 123" 50 | This will create a logfile at the specified path with the given message 51 | .EXAMPLE 52 | Write-Log -message "Message 123" 53 | This will create a logfile at the default path (c:\temp\logfile.log) with 54 | the given message 55 | #> 56 | param( 57 | $logfile = "C:\temp\logfile.log", 58 | [string]$message 59 | ) 60 | 61 | $date = Get-Date -Format "yyyy.MM.dd HH:mm:ss" 62 | 63 | if(Test-Path $logfile){ 64 | $loginit = $true 65 | } 66 | else{ 67 | $loginit = $false 68 | $logheader = @" 69 | ********************************** 70 | Logfile : $logfile 71 | Created : $(get-date) 72 | ********************************** 73 | "@ 74 | } 75 | 76 | if(!$loginit){ 77 | New-Item -ItemType File -Path $logfile 78 | write $logheader | Out-File $logfile -Encoding unicode 79 | write "$date : $message" | Out-File $logfile -Encoding unicode -Append 80 | } 81 | else{ 82 | write "$date : $message" | Out-File $logfile -Encoding unicode -Append 83 | } 84 | 85 | }#end function Write-Log 86 | 87 | function Get-VMDiskLimit { 88 | <# 89 | .SYNOPSIS 90 | Function for getting limits on a single VMDK 91 | .DESCRIPTION 92 | The function will get the IOPS limit on a specific 93 | VMDK 94 | .NOTES 95 | Info 96 | Author : Rudi Martinsen / Intility AS 97 | Date : 24/02-2018 98 | Version : 0.1.1 99 | Revised : 24/02-2018 100 | Changelog: 101 | 0.1.1 -- Added VM name to output 102 | .PARAMETER Harddisk 103 | VMDK to get limits from 104 | .EXAMPLE 105 | Get-VMDiskLimit -Harddisk $disk 106 | Gets the limits on a VMDK 107 | #> 108 | [cmdletbinding()] 109 | param( 110 | [Parameter(Mandatory=$true,ValueFromPipeline=$true,ParameterSetName="vmdk")] 111 | [Alias("VMDK")] 112 | [VMware.VimAutomation.ViCore.Types.V1.VirtualDevice.FlatHardDisk] 113 | $Harddisk 114 | ) 115 | 116 | $Harddisk.Parent | Get-VMResourceConfiguration | Select-Object -ExpandProperty DiskResourceConfiguration | Where-Object {$_.key -eq $Harddisk.ExtensionData.key} | Select-Object @{l="VM";e={$Harddisk.Parent.Name}},@{l="Label";e={$Harddisk.Name}},@{l="IOPSLimit";e={$_.DiskLimitIOPerSecond}} 117 | 118 | } 119 | 120 | function Set-VMDiskLimit { 121 | <# 122 | .SYNOPSIS 123 | Function for setting limits on a single VMDK 124 | .DESCRIPTION 125 | The function will get the IOPS limit on a specific 126 | VMDK 127 | .NOTES 128 | Info 129 | Author : Rudi Martinsen / Intility AS 130 | Date : 24/02-2018 131 | Version : 1.0.0 132 | Revised : 24/02-2018 133 | Changelog: 134 | .PARAMETER Harddisk 135 | VMDK to set limits on 136 | .PARAMETER IOPSLimit 137 | The IOPSLimit to set 138 | .EXAMPLE 139 | Set-VMDiskLimit -Harddisk $disk -IOPSLimit 500 140 | Sets the IOPS limit on the VMDK to 500 141 | #> 142 | [cmdletbinding()] 143 | param( 144 | [Parameter(Mandatory=$true,ValueFromPipeline=$true,ParameterSetName="vmdk")] 145 | [Alias("VMDK")] 146 | [VMware.VimAutomation.ViCore.Types.V1.VirtualDevice.FlatHardDisk] 147 | $Harddisk, 148 | [Parameter(Mandatory=$true)] 149 | [Alias("Limit")] 150 | [int] 151 | $IOPSLimit 152 | ) 153 | 154 | $result = Get-VMResourceConfiguration -VM $Harddisk.parent | Set-VMResourceConfiguration -Disk $Harddisk -DiskLimitIOPerSecond $IOPSLimit 155 | $result.DiskResourceConfiguration | Where-Object {$_.key -eq $Harddisk.ExtensionData.Key} 156 | 157 | } 158 | 159 | 160 | $scriptstart = Get-Date 161 | Write-Log -logfile $logfile -message "Script start" 162 | 163 | Write-Log -logfile $logfile -message "Connecting to vCenter" 164 | $vc_conn = Connect-VIServer $vcenter 165 | if($vc_conn){ 166 | Write-Log -logfile $logfile -message "Connected to vCenter $vcenter with sessionID: $($vc_conn.SessionId)" 167 | } 168 | else{ 169 | Write-Log -logfile $logfile -message "ERROR -- Couldn't connect to vCenter. Exiting" 170 | break 171 | } 172 | 173 | $policies2check = "NoRep 500 IOPS" 174 | 175 | $limitsSet = 0 176 | 177 | if($cluster){ 178 | $disks = Get-Cluster $cluster | Get-VM | Get-HardDisk 179 | } 180 | else{ 181 | $disks = Get-VM | Get-HardDisk 182 | } 183 | 184 | Write-Log -logfile $logfile -message "Found $($vms.count) VMs to process" 185 | 186 | foreach($disk in $disks){ 187 | $lapstart = Get-Date 188 | Clear-Variable limit,expectedLimit -ErrorAction SilentlyContinue 189 | Write-Log -logfile $logfile -message "Processing VMDK $($disk.name) for VM $($disk.Parent.Name)" 190 | $diskPolicy = Get-SpbmEntityConfiguration -HardDisk $disk 191 | if($diskPolicy.StoragePolicy){ 192 | if($diskPolicy.StoragePolicy.Name -in $policies2check){ 193 | $set = $false 194 | $expectedLimit = $diskPolicy.StoragePolicy.Name.Split(" ")[1] 195 | Write-Log -logfile $logfile -message "Checking VMDK limits" 196 | 197 | $limit = Get-VMDiskLimit -Harddisk $disk | Select-Object -ExpandProperty IOPSLimit 198 | if($limit -gt 0 -and $limit -ne $expectedLimit){ 199 | #Limit is set, but is not corresponding to expected limit 200 | Write-Log -logfile $logfile -message "Limit is set to $limit, but is not corresponding to expected limit of $expectedlimit" 201 | $set = $true 202 | } 203 | elseif($limit -eq $expectedlimit){ 204 | #Limit is set and corresponds to expected limit. Nothing to do... 205 | Write-Log -logfile $logfile -message "Limit is set and corresponds to expected limit" 206 | } 207 | else{ 208 | #Limit is not set or couldn't be retrieved! 209 | Write-Log -logfile $logfile -message "Limit is not set or couldn't be retrieved!" 210 | $set = $true 211 | } 212 | } 213 | } 214 | 215 | #Policy and limits are checked. If the $set variable is $true we will carry on with changing the limit 216 | if($set){ 217 | Write-Log -logfile $logfile -message "Setting limit on disk $($disk.name) to $expectedlimit" 218 | $result = Set-VMDiskLimit -Harddisk $disk -IOPSLimit $expectedLimit 219 | $limitsSet++ 220 | 221 | #Do a check on the limit set 222 | if($result.DiskLimitIOPerSecond -ne $expectedLimit){ 223 | Write-Log -logfile $logfile -message "ERROR -- Limit is still not set to the expected value" 224 | } 225 | } 226 | 227 | Write-Log -logfile $logfile -message "Finished processing disk" 228 | $lapend = Get-Date 229 | $lapSpan = New-TimeSpan -Start $lapstart -End $lapend 230 | Write-Log -logfile $logfile -message "Disk processed in $($lapSpan.TotalSeconds) seconds" 231 | 232 | } 233 | 234 | Write-Log -logfile $logfile -message "All disks processed. Limits were set on $limitsSet disk(s)" 235 | 236 | $scriptEnd = Get-Date 237 | $scriptSpan = New-TimeSpan -Start $scriptstart -End $scriptEnd 238 | Write-Log -logfile $logfile -message "Script processed in $($scriptSpan.TotalMinutes) minutes" 239 | -------------------------------------------------------------------------------- /vSAN/twonode_vsan_setup.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Script for automated setup of two node VSAN 4 | .DESCRIPTION 5 | The script creates a new cluster, adds 6 | the hosts, deploys the VSAN witness host, configures 7 | the network settings and standard ESXi settings for hosts. 8 | 9 | The script is purpose built for a customer case and might 10 | require some changes to work in other environments. 11 | The location parameter is key in determining the correct name 12 | for clusters and hosts. 13 | .NOTES 14 | Author: Rudi Martinsen / Intility AS 15 | Created: 04/10-2018 16 | Version: 1.1.0 17 | Revised: 25/10-2018 18 | Changelog: 19 | 1.1.0 -- Fixed teaming policy 20 | 1.0.0 -- Finished setup of VSAN cluster with fault domains, witness host etc 21 | 0.3.0 -- Parameterized names from loc.code 22 | .LINK 23 | https://www.rudimartinsen.com/2018/12/31/automating-two-node-vsan-cluster-setup/ 24 | .LINK 25 | https://www.jasemccarty.com/blog/deploy2node-ps1/ 26 | .PARAMETER LocationCode 27 | The location code of the new location, used to determine name for Cluster and hosts 28 | .PARAMETER WitnessIP 29 | IP address for the witness host 30 | .PARAMETER WitnessGW 31 | GW for the Witness Host 32 | #> 33 | param( 34 | [cmdletbinding()] 35 | [Parameter(Mandatory=$true)] 36 | $LocationCode, 37 | [Parameter(Mandatory=$true)] 38 | $WitnessIP, 39 | [Parameter(Mandatory=$false)] 40 | $WitnessGW 41 | ) 42 | 43 | Function runGuestOpInESXiVM() { 44 | param( 45 | $vm_moref, 46 | $guest_username = "root", 47 | $guest_password = "password", 48 | $guest_command_path, 49 | $guest_command_args 50 | ) 51 | 52 | # Guest Ops Managers 53 | $guestOpMgr = Get-View $session.ExtensionData.Content.GuestOperationsManager 54 | $authMgr = Get-View $guestOpMgr.AuthManager 55 | $procMgr = Get-View $guestOpMgr.processManager 56 | 57 | # Create Auth Session Object 58 | $auth = New-Object VMware.Vim.NamePasswordAuthentication 59 | $auth.username = $guest_username 60 | $auth.password = $guest_password 61 | $auth.InteractiveSession = $false 62 | 63 | # Program Spec 64 | $progSpec = New-Object VMware.Vim.GuestProgramSpec 65 | # Full path to the command to run inside the guest 66 | $progSpec.programPath = "$guest_command_path" 67 | $progSpec.workingDirectory = "/tmp" 68 | # Arguments to the command path, must include "++goup=host/vim/tmp" as part of the arguments 69 | $progSpec.arguments = "++group=host/vim/tmp $guest_command_args" 70 | 71 | # Issue guest op command 72 | $cmd_pid = $procMgr.StartProgramInGuest($vm_moref,$auth,$progSpec) 73 | } 74 | 75 | ####################### 76 | ## Static parameters ## 77 | ####################### 78 | $Vcenter = "your-vcenter-address" 79 | $DataCenter = "vc-datacenter-name" 80 | $WitnessFolder = "location-for-witness-host" 81 | 82 | $domainName = "domain.name" 83 | 84 | $ClusterName = "$LocationCode-VSAN" 85 | 86 | $ESXi1Name = "$LocationCode-esx-001" 87 | $ESXi2Name = "$LocationCode-esx-002" 88 | $ESXi3Name = "$LocationCode-esx-003" 89 | 90 | $WitnessName = "$LocationCode-esx-004" 91 | $WitnessFQDN = $WitnessName + $domainName 92 | 93 | $WitnessVLAN = 100 94 | $WitnessNetworkName = "$LocationCode-SRV-$WitnessVLAN" 95 | 96 | $WitnessTargetDSName = "$ESXi3Name-R1-2" 97 | $WitnessR5DS = "$ESXi3Name-R5-3" 98 | 99 | $VMNetworkVLAN = 200 100 | $VMNetworkName = "$LocationCode-VM-$VMNetworkVLAN" 101 | 102 | #IPs for the vmk's of the two vsan hosts which will be direct linked. 103 | $ESXi1vmk1Ip = "192.168.1.1" 104 | $ESXi1vmk2Ip = "192.168.2.1" 105 | $ESXi2vmk1Ip = "192.168.1.2" 106 | $ESXi2vmk2Ip = "192.168.2.2" 107 | $vmkMask = "255.255.255.0" 108 | 109 | $WitnessSN = "255.255.255.0" 110 | if(!$WitnessGW){ 111 | $splitWitnIp = $WitnessIP.split(".") 112 | $WitnessGW = $splitWitnIp[0] + "." + $splitWitnIp[1] + "." + $splitWitnIp[2] + ".1" 113 | } 114 | 115 | $DNSPrim = "1.1.1.1" 116 | $DNSSec = "2.2.2.2" 117 | 118 | $NTPServer = "3.3.3.3" 119 | $SyslogServer = "tcp://4.4.4.4:1514" 120 | 121 | $OVF_File = "c:\temp\VMware-vSAN-Witness-6.5.0.update01-5969303.ova" 122 | $OVF_DeploymentOption = "tiny" 123 | 124 | ################## 125 | ## Verification ## 126 | ################## 127 | $output = @" 128 | ####################################################################### 129 | 130 | The script will be run with the following parameters: 131 | 132 | ## vCenter info ## 133 | VCenter: $Vcenter 134 | DataCenter: $DataCenter 135 | 136 | ## Cluster info ## 137 | ClusterName: $ClusterName 138 | Witness Folder: $WitnessFolder 139 | 140 | ## Physical ESX info ## 141 | ESX-001: $ESXi1Name 142 | ESX-002: $ESXi1Name 143 | ESX-003: $ESXi1Name 144 | 145 | ## Datastores ## 146 | Witness datastore (R1): $WitnessTargetDSName 147 | Witness datastore (R5): $WitnessR5DS 148 | 149 | ## OVF ## 150 | OVF file: $OVF_File 151 | Deployment option: $OVF_DeploymentOption 152 | 153 | ## ESXi VMK ## 154 | ESX-001 vmk1: $ESXi1vmk1Ip 155 | ESX-001 vmk2: $ESXi1vmk2Ip 156 | 157 | ESX-002 vmk1: $ESXi2vmk1Ip 158 | ESX-002 vmk2: $ESXi2vmk2Ip 159 | 160 | Subnet mask: $vmkMask 161 | 162 | ## Witness network info ## 163 | Witness name: $WitnessName 164 | Witness FQDN: $WitnessFQDN 165 | Witness IP: $WitnessIP 166 | Witness GW: $WitnessGW 167 | Witness Subnet mask: $WitnessSN 168 | Witness network name (pg): $WitnessNetworkName 169 | 170 | ## VM network ## 171 | VM Network Name: $VMNetworkName 172 | 173 | ## DNS servers ## 174 | Primary: $DNSPrim 175 | Secondary: $DNSSec 176 | 177 | ## NTP and Syslog server ## 178 | NTP: $NTPServer 179 | Syslog: $SyslogServer 180 | 181 | ####################################################################### 182 | "@ 183 | Write-Output $output 184 | $answer = Read-Host -Prompt "Do you want to continue (y/n)?" 185 | if($answer -ne "y" -and $answer -ne "yes"){ 186 | break 187 | } 188 | else{ 189 | $output | Out-File -FilePath C:\temp\$LocationCode-VSAN-Setup-Params.txt 190 | } 191 | 192 | ################ 193 | ## User input ## 194 | ################ 195 | $start = Get-Date 196 | $esxCredentials = Get-Credential -UserName root -Message "Please specify esxi root credentials" 197 | 198 | ######################## 199 | ## Connect to vCenter ## 200 | ######################## 201 | Write-Output "Connecting to vCenter" 202 | $session = Connect-VIServer $vcenter 203 | 204 | ################################## 205 | ## Ping hosts before continuing ## 206 | ################################## 207 | if(!(Test-Connection -ComputerName $ESXi1Name -Count 2 -ErrorAction SilentlyContinue)){ 208 | Write-Error "Host $ESXi1Name not responding" 209 | break 210 | } 211 | if(!(Test-Connection -ComputerName $ESXi2Name -Count 2 -ErrorAction SilentlyContinue)){ 212 | Write-Error "Host $ESXi2Name not responding" 213 | break 214 | } 215 | if(!(Test-Connection -ComputerName $ESXi3Name -Count 2 -ErrorAction SilentlyContinue)){ 216 | Write-Error "Host $ESXi3Name not responding" 217 | break 218 | } 219 | 220 | #################### 221 | ## Create cluster ## 222 | #################### 223 | Write-Output "Creating cluster" 224 | if(Get-Cluster -Name $ClusterName -ErrorAction SilentlyContinue){ 225 | Write-Warning "Cluster already exists" 226 | $cluster = Get-Cluster -Name $ClusterName 227 | } 228 | else{ 229 | $cluster = New-Cluster -Name $ClusterName -Location $DataCenter -HAEnabled:$false -DrsEnabled:$true 230 | } 231 | 232 | ######################### 233 | ## Add host to cluster ## 234 | ######################### 235 | $added = $false 236 | Write-Output "Adding hosts to vCenter" 237 | if(!(Get-VMHost -Name $ESXi1Name*)){ 238 | $vmhost1 = Add-VMHost -Name $ESXi1Name -Location $cluster -Credential $esxCredentials -Force 239 | $added = $true 240 | } 241 | else{ 242 | $vmhost1 = Get-VMHost -Name $ESXi1Name* 243 | } 244 | if(!(Get-VMHost -Name $ESXi2Name*)){ 245 | $vmhost2 = Add-VMHost -Name $ESXi2Name -Location $cluster -Credential $esxCredentials -Force 246 | $added = $true 247 | } 248 | else{ 249 | $vmhost2 = Get-VMHost -Name $ESXi2Name* 250 | } 251 | if(!(Get-VMHost -Name $ESXi3Name*)){ 252 | $vmhost3 = Add-VMHost -Name $ESXi3Name -Location $WitnessFolder -Credential $esxCredentials -Force 253 | $added = $true 254 | } 255 | else{ 256 | $vmhost3 = Get-VMHost -Name $ESXi3Name* 257 | } 258 | 259 | $vmhosts = Get-VMHost $vmhost1,$vmhost2,$vmhost3 260 | 261 | ########################## 262 | ## Sleep for 30 seconds ## 263 | ########################## 264 | if($added){ 265 | Write-Output "Sleeping for 30 seconds..." 266 | Start-Sleep -seconds 30 267 | } 268 | 269 | ######################################################## 270 | ## Set hosts in maintenance and disable alarm actions ## 271 | ######################################################## 272 | Write-Output "Setting hosts in maintenance mode and disable alarm actions" 273 | $alarmMgr = Get-View AlarmManager 274 | $vmhost1 | Set-VMHost -State Maintenance -Confirm:$false | Out-Null 275 | $alarmMgr.EnableAlarmActions($vmhost1.ExtensionData.MoRef,$false) 276 | $vmhost2 | Set-VMHost -State Maintenance -Confirm:$false | Out-Null 277 | $alarmMgr.EnableAlarmActions($vmhost2.ExtensionData.MoRef,$false) 278 | $alarmMgr.EnableAlarmActions($vmhost3.ExtensionData.MoRef,$false) 279 | 280 | ######################### 281 | ## Host Network config ## 282 | ######################### 283 | Write-Output "Configure host networking" 284 | foreach($vmhost in $vmhosts){ 285 | Write-Output "Configuring host $($vmhost.name)" 286 | 287 | $hostAdapters = Get-VMHostNetworkAdapter -VMHost $vmhost -Physical 288 | $hostSwitches = Get-VirtualSwitch -VMHost $vmhost -Standard 289 | $vsw0 = $hostSwitches | where {$_.name -eq "vswitch0"} 290 | 291 | $vmnic1 = $hostAdapters | where {$_.name -eq "vmnic1"} 292 | 293 | Write-Output "Add vmnic1 to vSwitch0" 294 | Add-VirtualSwitchPhysicalNetworkAdapter -VMHostPhysicalNic $vmnic1 -VirtualSwitch $vsw0 -Confirm:$false 295 | 296 | Write-Output "Change NIC teaming policy on vSwitch0 to active/standby" 297 | $pol0 = Get-NicTeamingPolicy -VirtualSwitch $vsw0 298 | Set-NicTeamingPolicy -VirtualSwitchPolicy $pol0 -MakeNicActive vmnic0 -MakeNicStandby vmnic1 299 | 300 | if($vmhost -eq $vmhost3){ 301 | Write-Verbose "No more IP config for this host" 302 | continue 303 | } 304 | elseif($vmhost -eq $vmhost1){ 305 | $vmk1Ip = $ESXi1vmk1Ip 306 | $vmk2Ip = $ESXi1vmk2Ip 307 | } 308 | elseif($vmhost -eq $vmhost2){ 309 | $vmk1Ip = $ESXi2vmk1Ip 310 | $vmk2Ip = $ESXi2vmk2Ip 311 | } 312 | else{ 313 | Write-Error "Couldn't set vmkernel adapter IP" 314 | continue 315 | } 316 | 317 | Write-Output "Create switch and portgroup for VM traffic" 318 | $vsw1 = New-VirtualSwitch -VMHost $vmhost -Name vSwitch1 -Nic vmnic2,vmnic3 319 | Write-Output "Change NIC teaming policy on vSwitch1 to active/standby" 320 | $pol1 = Get-NicTeamingPolicy -VirtualSwitch $vsw1 321 | Set-NicTeamingPolicy -VirtualSwitchPolicy $pol1 -MakeNicActive vmnic0 -MakeNicStandby vmnic1 322 | 323 | $vmPG = New-VirtualPortGroup -VirtualSwitch $vsw1 -VLanId 20 -Name $VMNetworkName | Out-Null 324 | 325 | Write-Output "Create switches and portgroups for VMotion and VSAN traffic" 326 | $vsw2 = New-VirtualSwitch -VMHost $vmhost -Name vSwitch2 -Nic vmnic4 | Out-Null 327 | $vmPG1 = New-VirtualPortGroup -VirtualSwitch $vsw2 -Name "VSAN-vMotion-1" | Out-Null 328 | $vmk1 = New-VMHostNetworkAdapter -VMHost $vmhost -PortGroup $vmPG1 -VirtualSwitch $vsw2 -IP $vmk1Ip -SubnetMask $vmkMask -VMotionEnabled:$true -VsanTrafficEnabled:$true | Out-Null 329 | 330 | $vsw3 = New-VirtualSwitch -VMHost $vmhost -Name vSwitch3 -Nic vmnic5 331 | $vmPG2 = New-VirtualPortGroup -VirtualSwitch $vsw3 -Name "VSAN-vMotion-2" | Out-Null 332 | $vmk2 = New-VMHostNetworkAdapter -VMHost $vmhost -PortGroup $vmPG2 -VirtualSwitch $vsw3 -IP $vmk2Ip -SubnetMask $vmkMask -VMotionEnabled:$true -VsanTrafficEnabled:$true | Out-Null 333 | } 334 | 335 | ####################################### 336 | ## Create mgmt portgroup for witness ## 337 | ####################################### 338 | Write-Output "Creating management pg on Witness target host" 339 | $witnessTargetHost = Get-VMHost $ESXi3Name* 340 | $witvsw0 = Get-VirtualSwitch -VMHost $WitnessTargetHost -Name vSwitch0 341 | $createWitmgmtPg = New-VirtualPortGroup -VirtualSwitch $witvsw0 -Name $WitnessNetworkName -VLanId $WitnessVLAN 342 | $witmgmtPg = Get-VirtualPortGroup -VMHost $witnesstargethost -VirtualSwitch vswitch0 -Name $WitnessNetworkName 343 | 344 | ################################## 345 | ## Create datastore for witness ## 346 | ################################## 347 | Write-Output "Creating datastores on Witness target host" 348 | 349 | $hbas = Get-VMHostHba -VMHost $witnesstargethost 350 | $r1DiskLun = $hbas | where {$_.Type -eq "ParallelScsi"} | Get-ScsiLun 351 | $r5DiskLun = $hbas | where {$_.Type -eq "Block"} | Get-ScsiLun -CanonicalName naa* 352 | 353 | $r1DiskPath = $r1DiskLun.CanonicalName 354 | New-Datastore -VMHost $witnessTargetHost -Name $WitnessTargetDSName -Vmfs -FileSystemVersion 6 -Path $r1DiskPath | Out-Null 355 | 356 | $r5DiskPath = $r5DiskLun.CanonicalName 357 | New-Datastore -VMHost $witnessTargetHost -Name $WitnessR5DS -Vmfs -FileSystemVersion 6 -Path $r5DiskPath | Out-Null 358 | 359 | ##Scan for new datastore 360 | $witnessTargetDS = $witnessTargetHost | Get-Datastore $WitnessTargetDSName 361 | 362 | #################### 363 | ## Deploy witness ## 364 | #################### 365 | Write-Output "Deploying virtual Witness host - This will take some time...." 366 | 367 | $pass = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($esxCredentials.Password)) 368 | $ovfConfig = Get-OvfConfiguration -Ovf $OVF_File 369 | $ovfConfig.DeploymentOption.Value = $OVF_DeploymentOption 370 | $ovfConfig.NetworkMapping.Witness_Network.Value = "VM Network" 371 | $ovfConfig.NetworkMapping.Management_Network.Value = "VM Network" 372 | $ovfConfig.vsan.witness.root.passwd.Value = $pass 373 | 374 | Import-VApp -Source $OVF_File -OvfConfiguration $ovfConfig -Name $WitnessName -Location $witnessTargetHost -VMHost $witnessTargetHost -Datastore $witnessTargetDS -DiskStorageFormat EagerZeroedThick | Out-Null 375 | 376 | $witnessVM = Get-VM $WitnessName 377 | $witnessVM | Get-NetworkAdapter | Set-NetworkAdapter -NetworkName $witmgmtPg -StartConnected $true -Confirm:$false -ErrorAction Stop -ErrorVariable netErr | Out-Null 378 | if($netErr){ 379 | Write-Error "Couldn't change network adapter on witness appliance" 380 | } 381 | 382 | $witnessVM | Start-VM | Out-Null 383 | Write-Output "Waiting for VM Tools to Start" 384 | do { 385 | $toolsStatus = (Get-VM $WitnessName | Get-View).Guest.ToolsStatus 386 | Write-Output $toolsStatus 387 | sleep 5 388 | } until ( $toolsStatus -eq 'toolsOk' ) 389 | 390 | Write-Output "Configuring virtual Witness host" 391 | $Command_Path = '/bin/python' 392 | $CMD_MGMT = '/bin/esxcli.py network ip interface ipv4 set -i vmk0 -I ' + $WitnessIP + ' -N ' + $WitnessSN + ' -t static;/bin/esxcli.py network ip route ipv4 add -N defaultTcpipStack -n default -g ' + $WitnessGW + ';/bin/esxcli.py network vswitch standard portgroup set -p "Management Network"' 393 | $CMD_DNS = '/bin/esxcli.py network ip dns server add --server=' + $DNSPrim + ';/bin/esxcli.py network ip dns server add --server=' + $DNSSec + ';/bin/esxcli.py system hostname set --fqdn=' + $WitnessFQDN 394 | 395 | runGuestOpInESXiVM -vm_moref $witnessVM.ExtensionData.MoRef -guest_command_path $command_path -guest_command_args $CMD_MGMT -guest_password $pass 396 | runGuestOpInESXiVM -vm_moref $witnessVM.ExtensionData.MoRef -guest_command_path $command_path -guest_command_args $CMD_DNS -guest_password $pass 397 | 398 | ###################### 399 | ## Add witness host ## 400 | ###################### 401 | Write-Output "Adding virtual Witness host to vCenter" 402 | 403 | if(!(Test-Connection -ComputerName $WitnessName -Count 3 -ErrorAction SilentlyContinue)){ 404 | Write-Error "Host $WitnessName not responding" 405 | break 406 | } 407 | 408 | $witnessHost = Add-VMHost -Name $WitnessFQDN -Location $WitnessFolder -Credential $esxCredentials -Force | Out-Null 409 | 410 | Write-Output "Sleeping for 20 seconds" 411 | Start-Sleep -Seconds 20 412 | 413 | ################################### 414 | ## Network settings witness host ## 415 | ################################### 416 | #Enable VSAN on vmk0 and remove witnessSwitch 417 | Write-Output "Configuring network on virtual Witness host" 418 | 419 | $witnessVSW0 = Get-VirtualSwitch -VMHost $witnessHost -Standard -Name vSwitch0 420 | $witnessVMK0 = Get-VMHostNetworkAdapter -VMHost $witnessHost -VirtualSwitch $witnessVSW0 -VMKernel -Name vmk0 421 | Set-VMHostNetworkAdapter -VirtualNic $witnessVMK0 -VsanTrafficEnabled:$true -Confirm:$false | Out-Null 422 | $witnessVMK1 = Get-VMHostNetworkAdapter -VMHost $witnessHost -VMKernel -Name vmk1 423 | Remove-VMHostNetworkAdapter -Nic $witnessVMK1 -Confirm:$false | Out-Null 424 | $witnessSwitch = Get-VirtualSwitch -VMHost $witnessHost -Standard -Name witnessSwitch 425 | $witnessSwitch | Remove-VirtualSwitch -Confirm:$false | Out-Null 426 | 427 | ######################################## 428 | ## Standard ESXi config for all hosts ## 429 | ######################################## 430 | $vmhosts += $witnessHost 431 | 432 | Write-Output "Setting standard config for ESXi" 433 | foreach($vmhost in $vmhosts){ 434 | Write-Output "Configuring host $($vmhost.name)" 435 | 436 | $settings = $vmhost | Get-AdvancedSetting 437 | 438 | # Remove ssh warning.. 439 | Write-Output "Configuring ssh warning" 440 | $settings | where {$_.name -eq "UserVars.SuppressShellWarning"} | Set-AdvancedSetting -Value '1' -Confirm:$false | Out-Null 441 | 442 | Write-Output "Configuring syslog" 443 | $vmhost | Set-VMHostSysLogServer -SyslogServer $syslogserver | Out-Null 444 | $vmhost | Get-VMHostService | where {$_.Key -eq "vmsyslogd"} | Restart-VMHostService -Confirm:$false | Out-Null 445 | $vmhost | Get-VMHostFirewallException | where {$_.Name -eq "syslog"} | Set-VMHostFirewallException -Enabled $true | Out-Null 446 | 447 | Write-Output "Configuring ssh" 448 | $vmhost | Get-VMHostService | where {$_.Key -eq "TSM-SSH"} | Set-VMHostService -Policy "On" | Out-Null 449 | $vmhost | Get-VMHostFirewallException | where {$_.Name -eq "SSH Server"} | Set-VMHostFirewallException -Enabled $true | Out-Null 450 | $vmhost | Get-VMHostService | where {$_.Key -eq "TSM-SSH"} | Start-VMHostService -Confirm:$false | Out-Null 451 | 452 | Write-Output "Configuring ntp" 453 | $vmhost | Get-VMHostService | where {$_.Key -eq "ntpd"} | Set-VMHostService -Policy "On" | Out-Null 454 | if(($vmhost | Get-VMHostNtpServer) -ne $NTPServer){ 455 | $vmhost | Add-VMHostNtpServer -NtpServer $ntpServer | Out-Null 456 | $vmhost | Get-VMHostFirewallException | where {$_.Name -eq "NTP Client"} | Set-VMHostFirewallException -Enabled $true | Out-Null 457 | $vmhost | Get-VMHostService | where {$_.Key -eq "ntpd"} | Restart-VMHostService -Confirm:$false | Out-Null 458 | } 459 | 460 | Write-Output "Configuring power settings" 461 | (Get-View ($vmhost | Get-View).ConfigManager.PowerSystem).ConfigurePowerPolicy(1) 462 | 463 | Write-Output "Configuring DNS" 464 | $vmhost | Get-VMHostNetwork | Set-VMHostNetwork -DomainName $domainName -DNSAddress $DNSPrim , $DNSSec -Confirm:$false | Out-Null 465 | } 466 | 467 | ################# 468 | ## Enable VSAN ## 469 | ################# 470 | Write-Output "Enabling VSAN on cluster" 471 | $cluster | Set-Cluster -VsanEnabled:$true -Confirm:$false 472 | 473 | ################ 474 | ## Diskgroups ## 475 | ################ 476 | Write-Output "Creating VSAN disk groups" 477 | 478 | $vsanhosts = $vmhost1,$vmhost2 479 | foreach($vsanhost in $vsanhosts){ 480 | Write-Output "Exiting maintenance mode for host $($vsanhost.name)" 481 | $vsanhost | Set-VMHost -State Connected | Out-Null 482 | while((Get-VMHost $vsanhost).State -ne "Connected"){ 483 | Start-Sleep -seconds 5 484 | } 485 | 486 | Write-Output "Creating disk groups for host $($vsanhost.name)" 487 | $hba = Get-VMHostHba -VMHost $vsanhost -Device vmhba1 488 | $luns = $hba | Where-Object {$_.Type -eq "Block"} | Get-ScsiLun -CanonicalName naa* 489 | 490 | $ssdDisks = $luns | Where-Object {$_.IsSsd} 491 | $capDisks = $luns | Where-Object {!$_.IsSsd} 492 | 493 | $capDiskArray1 = @() 494 | $capDiskArray2 = @() 495 | $capDiskArray1 += $capDisks[0].CanonicalName 496 | $capDiskArray1 += $capDisks[1].CanonicalName 497 | $capDiskArray2 += $capDisks[2].CanonicalName 498 | $capDiskArray2 += $capDisks[3].CanonicalName 499 | 500 | New-VsanDiskGroup -VMHost $vsanhost -SsdCanonicalName $ssdDisks[0].CanonicalName -DataDiskCanonicalName $capDiskArray1 | Out-Null 501 | New-VsanDiskGroup -VMHost $vsanhost -SsdCanonicalName $ssdDisks[1].CanonicalName -DataDiskCanonicalName $capDiskArray2 | Out-Null 502 | } 503 | 504 | $witnessHba = Get-VMHostHba -VMHost $witnessHost -Device vmhba1 505 | $witnessLuns = $witnessHba | Get-ScsiLun 506 | $ssdLun = $witnessLuns | Where-Object {$_.CanonicalName -eq "mpx.vmhba1:c0:t2:l0"} 507 | $capLun = $witnessLuns | Where-Object {$_.CanonicalName -eq "mpx.vmhba1:c0:t1:l0"} 508 | 509 | Write-Output "Sleeping for 20 seconds" 510 | Start-Sleep -Seconds 20 511 | 512 | ############################### 513 | ## Configure witness traffic ## 514 | ############################### 515 | Write-Output "Configure witness traffic on management vmk" 516 | foreach($vsanhost in $vsanhosts){ 517 | $esxcli = Get-EsxCli -VMHost $vsanhost -V2 518 | $esxcli.vsan.network.ip.add.Invoke(@{interfacename="vmk0";traffictype="witness"}) 519 | } 520 | 521 | Write-Output "Sleeping for 20 seconds" 522 | Start-Sleep -Seconds 20 523 | ######################### 524 | ## Reconfigure cluster ## 525 | ######################### 526 | Write-Output "Configuring VSAN Fault domains" 527 | 528 | $primaryFd = New-VsanFaultDomain -Name 'Preferred' -VMHost $vmhost1 | Out-Null 529 | $secondaryFd = New-VsanFaultDomain -Name 'Secondary' -VMHost $vmhost2 | Out-Null 530 | 531 | Write-Output "Reconfiguring VSAN cluster to include witness host and fault domains" 532 | Set-VsanClusterConfiguration -Configuration $cluster -StretchedClusterEnabled $true -PreferredFaultDomain $primaryFd -WitnessHost $witnessHost -WitnessHostCacheDisk $ssdLun.CanonicalName -WitnessHostCapacityDisk $capLun.CanonicalName -PerformanceServiceEnabled:$true -Confirm:$false | Out-Null 533 | 534 | $stop = Get-Date 535 | $timespan = New-TimeSpan -Start $start -End $stop 536 | $mins = [math]::Round($timespan.TotalMinutes,2) 537 | 538 | Write-Output "#####################################" 539 | Write-Output "## Script finished in $mins minutes ##" 540 | Write-Output "#####################################" --------------------------------------------------------------------------------