From 11330a67ac7d426c92f650aa5c7395ad8c73146e Mon Sep 17 00:00:00 2001 From: simonfangyingzhang Date: Tue, 22 Aug 2017 07:44:26 +0100 Subject: [PATCH 01/23] New-VIProperty KMSserver per Mike Foley New-VIProperty KMSserver per Mike Foley --- Modules/VMware.VMEncryption/VMware.VMEncryption.psm1 | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Modules/VMware.VMEncryption/VMware.VMEncryption.psm1 b/Modules/VMware.VMEncryption/VMware.VMEncryption.psm1 index c350955..8d19b50 100644 --- a/Modules/VMware.VMEncryption/VMware.VMEncryption.psm1 +++ b/Modules/VMware.VMEncryption/VMware.VMEncryption.psm1 @@ -83,6 +83,13 @@ New-VIProperty -Name EncryptionKeyId -ObjectType HardDisk -Value { } } -BasedOnExtensionProperty 'Backing.KeyId' -Force | Out-Null +New-VIProperty -Name KMSserver -ObjectType VMHost -Value { + Param ($VMHost) + if ($VMHost.CryptoSafe) { + $VMHost.ExtensionData.Runtime.CryptoKeyId.ProviderId.Id + } +} -BasedOnExtensionProperty 'Runtime.CryptoKeyId.ProviderId.Id' -Force | Out-Null + Function Enable-VMHostCryptoSafe { <# .SYNOPSIS From e70e50600fcbc7a674943881e2e123812ce394e5 Mon Sep 17 00:00:00 2001 From: Jim Jones Date: Wed, 30 Aug 2017 12:34:03 -0700 Subject: [PATCH 02/23] Create Get-VMToolsParts.ps1 Outputs the VMware Tool component installation state for all running Windows VMs in your environment. --- Scripts/Get-VMToolsParts.ps1 | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 Scripts/Get-VMToolsParts.ps1 diff --git a/Scripts/Get-VMToolsParts.ps1 b/Scripts/Get-VMToolsParts.ps1 new file mode 100644 index 0000000..c4a3d51 --- /dev/null +++ b/Scripts/Get-VMToolsParts.ps1 @@ -0,0 +1,11 @@ +$vms = Get-VM | where {$_.PowerState -eq "PoweredOn" -and $_.GuestId -match "Windows"} + +ForEach ($vm in $vms){ + Write-Host $vm + $namespace = "root\CIMV2" + $componentPattern = "hcmon|vmci|vmdebug|vmhgfs|VMMEMCTL|vmmouse|vmrawdsk|vmxnet|vmx_svga" + (Get-WmiObject -class Win32_SystemDriver -computername $vm -namespace $namespace | + where-object { $_.Name -match $componentPattern } | + Format-Table -Auto Name,State,StartMode,DisplayName + ) +} From b8d0d1071655eb1c2904384a48673b4a3176353c Mon Sep 17 00:00:00 2001 From: = Date: Wed, 30 Aug 2017 14:48:03 -0700 Subject: [PATCH 03/23] Added CIVM Data gathering function for vCD export/Migration --- Scripts/Get-CIVMData.ps1 | 245 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 245 insertions(+) create mode 100644 Scripts/Get-CIVMData.ps1 diff --git a/Scripts/Get-CIVMData.ps1 b/Scripts/Get-CIVMData.ps1 new file mode 100644 index 0000000..f7e83b3 --- /dev/null +++ b/Scripts/Get-CIVMData.ps1 @@ -0,0 +1,245 @@ +Function Get-CIVMData +{ +<# + .SYNOPSIS + Gathers information about a target CIVM + + .DESCRIPTION + This function gathers CIVM Name, Parent vApp (obj), Parent vApp Name, All network adapters + (including IP, NIC index, and network), and vCenter VMX path details returning the resulting + ordered list. + + .PARAMETER CIVM + The target vCloud VM from which information will be gathered + + .NOTES + Author: Brian Marsh + Version: 1.0 +#> + + [CmdletBinding()] + Param ( + [Parameter( + Position=0, + Mandatory=$true, + ValueFromPipeline=$true, + ValueFromPipelineByPropertyName=$true) + ] + [VMware.VimAutomation.Cloud.Types.V1.CIVM] $CIVM + ) + BEGIN + { + + } + + PROCESS + { + $NewObj = [Ordered]@{} + $NewObj.GetCIVMData = @{} + $NewObj.GetCIVMData.Successful = $true + + # Get the vCenter VM from the vCloud VM object + $vm = $civm | Get-VM -Debug:$False -Verbose:$False + + Write-Verbose "Storing CIVM Name: $($CIVM.Name)/ Status: $($CIVM.Status)" + $NewObj.Name = $CIVM.Name + $NewObj.Status = $CIVM.Status + + # Do some logic here, if the VM name matches "tmpl" or "tpl", it's a template. Grab the vApp name and set "New Name" + If ($CIVM.Name -match "tmpl" -or $CIVM.Name -match "tpl") + { + $NewObj.NewName = $CIVM.VApp.Name + } + Else + { + $NewObj.NewName = $CIVM.Name + } + + Write-Verbose "Recording Reservations" + $NewObj.Reservations = @{} + $NewObj.Reservations.CPU = @{} + $NewObj.Reservations.Memory = @{} + + $NewObj.Reservations.CPU.Reservation = $vm.ExtensionData.ResourceConfig.CpuAllocation.Reservation + $NewObj.Reservations.CPU.Limit = $vm.ExtensionData.ResourceConfig.CpuAllocation.Limit + $NewObj.Reservations.Memory.Reservation = $vm.ExtensionData.ResourceConfig.MemoryAllocation.Reservation + $NewObj.Reservations.Memory.Limit = $vm.ExtensionData.ResourceConfig.MemoryAllocation.Limit + + # Get the UUid from the Id, split out the UUID and pass it along + # Sample Id: urn:vcloud:vm:d9ca710d-cdf2-44eb-a274-26e1dcfd01bb + Write-Verbose "Storing CIVM UUID: $(($CIVM.Id).Split(':')[3])" + $NewObj.Uuid = ($CIVM.Id).Split(':')[3] + + Write-Verbose "Gathering Network details" + $vAppNetworkAdapters = @() + $NetworkAdapters = Get-CINetworkAdapter -VM $civm -Debug:$False -Verbose:$False + + foreach ($networkAdapter in $networkAdapters) + { + # Remove any existing VMNIC variables + Remove-Variable -Name VMNic -ErrorAction SilentlyContinue + + $vAppNicInfo = [Ordered]@{} + $vAppNicInfo.NIC = ("NIC" + $networkAdapter.Index) + $vAppNicInfo.Index = $networkAdapter.Index + $vAppNicInfo.Connected = $networkAdapter.Connected + $vAppNicInfo.ExternalIP = $networkAdapter.IpAddress + $vAppNicInfo.InternalIP = $networkAdapter.ExternalIpAddress + $vAppNicInfo.MacAddress = $networkAdapter.MACAddress + + $vAppNicInfo.vAppNetwork = [Ordered]@{} + $vAppNicInfo.vAppNetwork.Name = $networkAdapter.VAppNetwork.Name + + <# + There is a chance that the vApp Network Name may not match a PortGroup which causes issues upon importing the VM after migration. + To fix this issue, we'll try to find get the PortGroup in this data gathering stage. If it is not found, we'll move on to attempted + remediation: + 1) Get the vCenter VM network adapter that corresponds to this vCloud Director VM network adapter (where MAC Addresses match) + 2) If the vCenter VM network adapter's network name doesn't match 'none' (indicating the VM is powered off) and the vCenter Network + name does not match the vCloud Director network name, set this target object's vAppNetwork Name to the vCenter PortGroup + 3) If the vCenter VM network adapter's network name is 'none' then this VM is probably powered off and the network information is + not defined in vCenter. In this case, we mark the get-data as unsuccessful, set an error message and return. + #> + try + { + $vm | Get-VMHost -Debug:$false -Verbose:$false | Get-VDSwitch -Debug:$false -Verbose:$false -ErrorAction Stop | ` + Get-VDPortgroup -name $networkAdapter.vAppNetwork.Name -Debug:$false -Verbose:$false -ErrorAction Stop | Out-Null + } + catch + { + Write-Debug "Portgroup not found by name $($networkAdapter.vAppNetwork.Name), Debug?" + Write-Verbose "Portgroup not found by name $($networkAdapter.vAppNetwork.Name), attempting fall back." + # Get VIVM network adapter where adapter mac matches vappnicinfo MacAddress + $VMNic = $vm | Get-NetworkAdapter -Debug:$false -Verbose:$false | Where-Object { $_.MacAddress -eq $vAppNicInfo.MacAddress } + + # If VMNic Network Name doesn't match 'none' and doesn't match the vAppNetworkName, set vAppNetwork name to VMNic Network name + If ( ($VMNic.NetworkName -notlike 'none') -and ($VMNic.NetworkName -ne $vAppNicInfo.vAppNetwork.Name)) + { + $vAppNicInfo.vAppNetwork.Name = $VMNic.NetworkName + } + else + { + Write-Debug "Tried to recover from missing network port group. Failed. Debug?" + $ErrorMessage = "VM [ $($CIVM.Name) ] has vAppNetwork connection that doesn't exist in vCenter [ $($vAppNicInfo.vAppNetwork.Name) ]" + $NewObj.GetCIVMData.Successful = $False + $NewObj.GetCIVMData.Error = $ErrorMessage + Write-Error $ErrorMessage + + #Return whatever object we have at this point + $NewObj + + Return + } + + } + + $vAppNetworkAdapters += $vAppNicInfo + } + + Write-Verbose "Checking for Duplicate name upon Import" + Try + { + $DupeVM = Get-VM -Name $NewObj.NewName -Debug:$false -Verbose:$false -ErrorAction Stop -ErrorVariable DupeVM + If ($DupeVM) + { + $NewObj.GetCIVMData.Successful = $False + $NewObj.GetCIVMData.Error = "VM with name $($NewObj.NewName) already exists in vCenter" + Write-Error "VM with name $($NewObj.NewName) already exists in vCenter" + + #Return whatever object we have at this point + $NewObj + + return + } + } + Catch + { + Write-Verbose "No Duplicate Name Found!" + } + + $NewObj.vAppNetworkAdapters = $vAppNetworkAdapters + + Write-Verbose "Setting VIVIM object, parent vApp details, and CIVM object" + try + { + $NewObj.VIVM = $vm + $NewObj.ToolsStatus = $vm.ExtensionData.Guest.ToolsStatus + $NewObj.ToolsRunningStatus = $vm.ExtensionData.Guest.ToolsRunningStatus + $NewObj.HasSnapshots = ($vm | Get-Snapshot -Debug:$false -Verbose:$false -ErrorAction Stop | Select-Object Name, Description,VMId) + $NewObj.NeedsConsolidation = $vm.ExtensionData.Runtime.ConsolidationNeeded + $NewObj.OldMoref = $vm.Id + $NewObj.VmPathName = $vm.ExtensionData.Config.Files.VmPathName + $NewObj.ParentVApp = $CIVM.VApp.Name + $NewObj.StorageReservation = ($vm |Get-DatastoreCluster -Debug:$false -Verbose:$false -ErrorAction Stop | Select-Object -ExpandProperty Name) + $NewObj.CIVMId = $CIVM.Id + } + catch + { + $NewObj.GetCIVMData.Successful = $False + $NewObj.GetCIVMData.Error = "VM [ $($CIVM.Name) ] something went wrong while gathering details: $_" + Write-Debug "VM [ $($CIVM.Name) ] something went wrong while gathering details: $_, Debug" + Write-Error "VM [ $($CIVM.Name) ] something went wrong while gathering details: $_. " + + #Return whatever object we have at this point + $NewObj + + Return + } + + # If ToolsStatus is not 'toolsOk' and status is not "PoweredOn", bomb out. We won't be able to power this VM off later. + If ($NewObj.ToolsRunningStatus -ne 'guestToolsRunning' -and $NewObj.status -eq "PoweredOn") + { + $NewObj.GetCIVMData.Successful = $False + $NewObj.GetCIVMData.Error = "VM [ $($CIVM.Name) ] tools are not running but the VM is powered On. Fix and try again." + Write-Debug "VM [ $($CIVM.Name) ] tools are not running but the VM is powered On, Debug" + Write-Error "VM [ $($CIVM.Name) ] tools are not running but the VM is powered On. " + + #Return whatever object we have at this point + $NewObj + + Return + } + + If ($NewObj.HasSnapshots) + { + $NewObj.GetCIVMData.Successful = $False + $NewObj.GetCIVMData.Error = "VM [ $($CIVM.Name) ] has snapshots. Remove before trying again." + Write-Debug "VM [ $($CIVM.Name) ] has snapshots. Remove before trying again, Debug" + Write-Error "VM [ $($CIVM.Name) ] has snapshots. Remove before trying again." + + #Return whatever object we have at this point + $NewObj + + Return + } + + Write-Verbose "Determining the VMX Path for this VM" + + # Get this VM's path on disk + $vmPathName = $vm.ExtensionData.Config.Files.VmPathName + + # Determine in which Datacenter this VM resides + $datacenter = $vm | get-Datacenter -Debug:$False -Verbose:$False | Select-Object -expand name + + # Split out the datastore from the path name + $datastore = $vmPathName.Split("]")[0].split("[")[1] + + # Split out the folder from the path name + $vmFolderPath = $vmPathName.Split("/")[0].split("]")[1].trim() + + # Re-combine into a valid folder path + $vmxPath = "vmstore:\$($datacenter)\$($datastore)\$vmFolderPath" + + Write-Verbose "VMXPath $vmxPath" + $NewObj.vmxPath = $vmxPath + + $NewObj + + } + + END + { + Write-Debug "About to exit Get-CIVMData, anything else?" + Write-Verbose "Exited Get-CIVMData" + } +} From 070de5d0bdf3d7e401598de85035a1fd7b6fb513 Mon Sep 17 00:00:00 2001 From: Derek-Charlekston Date: Wed, 30 Aug 2017 17:26:53 -0500 Subject: [PATCH 04/23] Create get-ping.ps1 --- get-ping.ps1 | 53 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 get-ping.ps1 diff --git a/get-ping.ps1 b/get-ping.ps1 new file mode 100644 index 0000000..77e3e59 --- /dev/null +++ b/get-ping.ps1 @@ -0,0 +1,53 @@ +Function Get-PingStatus + { + param( + [Parameter(ValueFromPipeline=$true)] + [string]$device, + + [validateSet("Online","Offline","ObjectTable")] + [String]$getObject + ) + +begin{ + $hash = @() + + } +process{ + + $device| foreach { + if (Test-Connection $_ -Count 1 -Quiet) { + + if(-not($GetObject)){write-host -ForegroundColor green "Online: $_ "} + + $Hash = $Hash += @{Online="$_"} + }else{ + + if(-not($GetObject)){write-host -ForegroundColor Red "Offline: $_ "} + + $Hash = $Hash += @{Offline="$_"} + } + } + } + +end { + if($GetObject) { + + $Global:Objects = $Hash | foreach { [PSCustomObject]@{ + + DeviceName = $_.Values| foreach { "$_" } + Online = $_.Keys| where {$_ -eq "Online"} + offline = $_.Keys| where {$_ -eq "Offline"} + } + } + + Switch -Exact ($GetObject) + { + + 'Online' { $Global:Objects| where 'online'| select -ExpandProperty DeviceName } + 'Offline' { $Global:Objects| where 'offline'| select -ExpandProperty DeviceName } + 'ObjectTable' { return $Global:Objects } + } + + } + } +} From 98997718b8634f3408d818f98352378251118e51 Mon Sep 17 00:00:00 2001 From: = Date: Wed, 30 Aug 2017 15:29:02 -0700 Subject: [PATCH 05/23] updated code for clarity --- Scripts/Get-CIVMData.ps1 | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/Scripts/Get-CIVMData.ps1 b/Scripts/Get-CIVMData.ps1 index f7e83b3..7215cfc 100644 --- a/Scripts/Get-CIVMData.ps1 +++ b/Scripts/Get-CIVMData.ps1 @@ -44,16 +44,6 @@ Function Get-CIVMData Write-Verbose "Storing CIVM Name: $($CIVM.Name)/ Status: $($CIVM.Status)" $NewObj.Name = $CIVM.Name $NewObj.Status = $CIVM.Status - - # Do some logic here, if the VM name matches "tmpl" or "tpl", it's a template. Grab the vApp name and set "New Name" - If ($CIVM.Name -match "tmpl" -or $CIVM.Name -match "tpl") - { - $NewObj.NewName = $CIVM.VApp.Name - } - Else - { - $NewObj.NewName = $CIVM.Name - } Write-Verbose "Recording Reservations" $NewObj.Reservations = @{} From cd98f8672dfb7e52ebfdc00e456fcae7122ce6ab Mon Sep 17 00:00:00 2001 From: VTsnowboarder42 <31492211+VTsnowboarder42@users.noreply.github.com> Date: Wed, 30 Aug 2017 18:43:43 -0400 Subject: [PATCH 06/23] Create Get-BasicVMCapacityReport Create a basic VM capacity report --- Scripts/Get-BasicVMCapacityReport | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 Scripts/Get-BasicVMCapacityReport diff --git a/Scripts/Get-BasicVMCapacityReport b/Scripts/Get-BasicVMCapacityReport new file mode 100644 index 0000000..edbe9c7 --- /dev/null +++ b/Scripts/Get-BasicVMCapacityReport @@ -0,0 +1,30 @@ + +$myCol = @() +$start = (Get-Date).AddDays(-30) +$finish = Get-Date +$cluster= "Demo" + +$objServers = Get-Cluster $cluster | Get-VM +foreach ($server in $objServers) { + if ($server.guest.osfullname -ne $NULL){ + if ($server.guest.osfullname.contains("Windows")){ + $stats = get-stat -Entity $server -Stat "cpu.usage.average","mem.usage.average" -Start $start -Finish $finish + + $ServerInfo = "" | Select-Object vName, OS, Mem, AvgMem, MaxMem, CPU, AvgCPU, MaxCPU, pDisk, Host + $ServerInfo.vName = $server.name + $ServerInfo.OS = $server.guest.osfullname + $ServerInfo.Host = $server.vmhost.name + $ServerInfo.Mem = $server.memoryGB + $ServerInfo.AvgMem = $("{0:N2}" -f ($stats | Where-Object {$_.MetricId -eq "mem.usage.average"} | Measure-Object -Property Value -Average).Average) + $ServerInfo.MaxMem = $("{0:N2}" -f ($stats | Where-Object {$_.MetricId -eq "mem.usage.average"} | Measure-Object -Property Value -Maximum).Maximum) + $ServerInfo.CPU = $server.numcpu + $ServerInfo.AvgCPU = $("{0:N2}" -f ($stats | Where-Object {$_.MetricId -eq "cpu.usage.average"} | Measure-Object -Property Value -Average).Average) + $ServerInfo.MaxCPU = $("{0:N2}" -f ($stats | Where-Object {$_.MetricId -eq "cpu.usage.average"} | Measure-Object -Property Value -Maximum).Maximum) + $ServerInfo.pDisk = [Math]::Round($server.ProvisionedSpaceGB,2) + + $mycol += $ServerInfo + } + } +} + +$myCol | Sort-Object vName | Export-Csv "VM_report.csv" -NoTypeInformation From b21d2079caf574c8625f0bf76d021aede94dfd58 Mon Sep 17 00:00:00 2001 From: kdmhorn Date: Wed, 30 Aug 2017 18:57:55 -0400 Subject: [PATCH 07/23] Create vmCreationNotes.ps1 --- Scripts/vmCreationNotes.ps1 | 191 ++++++++++++++++++++++++++++++++++++ 1 file changed, 191 insertions(+) create mode 100644 Scripts/vmCreationNotes.ps1 diff --git a/Scripts/vmCreationNotes.ps1 b/Scripts/vmCreationNotes.ps1 new file mode 100644 index 0000000..2a805ca --- /dev/null +++ b/Scripts/vmCreationNotes.ps1 @@ -0,0 +1,191 @@ +<# +.SYNOPSIS + VM_CreationNotes to replace the Notes on newly deployed virtual machines with + information regarding there deployment, including date, time, user and method + of deployment. + +.DESCRIPTION + VM_CreationNotes is run daily as a scheduled task requiring no interaction. + The script will take in vCenter events for the latest 24 hour period filtering + for vm creation, clone or vapp deployment and parse the data. + Utilizes GET-VIEventsPlus by Luc Dekens for faster event gathering +.NOTES + File Name : VM_CreationNotes.ps1 + Author : KWH + Version : 1.03 + License : GNU GPL 3.0 www.gnu.org/licenses/gpl-3.0.en.html + +.INPUTS + No inputs required +.OUTPUTS + No Output is produced + +.PARAMETER config + No Parameters + +.PARAMETER Outputpath + No Parameters + +.PARAMETER job + No Parameters +.CHANGE LOG + #20170301 KWH - Removed canned VM Initialize script in favor of get-module + #20170303 KWH - Change VIEvent Call to date range rather than maxSamples + # KWH - Optimized event call where to array match + # KWH - Updated Synopsis and Description + # KWH - Changed vcenter list to text file input + # KWH - Added Register Events to list + # KWH - Included Get-VIEventPlus by LucD + #20170321 KWH - Added event $VIEvent array declaration/reset and $VM reset on loops + # KWH - Converted returned events to Local Time +#> + +<# + .SYNOPSIS Function GET-VIEventPlus Returns vSphere events + .DESCRIPTION The function will return vSphere events. With + the available parameters, the execution time can be + improved, compered to the original Get-VIEvent cmdlet. + .NOTES Author: Luc Dekens + .PARAMETER Entity + When specified the function returns events for the + specific vSphere entity. By default events for all + vSphere entities are returned. + .PARAMETER EventType + This parameter limits the returned events to those + specified on this parameter. + .PARAMETER Start + The start date of the events to retrieve + .PARAMETER Finish + The end date of the events to retrieve. + .PARAMETER Recurse + A switch indicating if the events for the children of + the Entity will also be returned + .PARAMETER User + The list of usernames for which events will be returned + .PARAMETER System + A switch that allows the selection of all system events. + .PARAMETER ScheduledTask + The name of a scheduled task for which the events + will be returned + .PARAMETER FullMessage + A switch indicating if the full message shall be compiled. + This switch can improve the execution speed if the full + message is not needed. + .EXAMPLE + PS> Get-VIEventPlus -Entity $vm + .EXAMPLE + PS> Get-VIEventPlus -Entity $cluster -Recurse:$true + #> + function Get-VIEventPlus { + + param( + [VMware.VimAutomation.ViCore.Impl.V1.Inventory.InventoryItemImpl[]]$Entity, + [string[]]$EventType, + [DateTime]$Start, + [DateTime]$Finish = (Get-Date), + [switch]$Recurse, + [string[]]$User, + [Switch]$System, + [string]$ScheduledTask, + [switch]$FullMessage = $false + ) + + process { + $eventnumber = 100 + $events = @() + $eventMgr = Get-View EventManager + $eventFilter = New-Object VMware.Vim.EventFilterSpec + $eventFilter.disableFullMessage = ! $FullMessage + $eventFilter.entity = New-Object VMware.Vim.EventFilterSpecByEntity + $eventFilter.entity.recursion = &{if($Recurse){"all"}else{"self"}} + $eventFilter.eventTypeId = $EventType + if($Start -or $Finish){ + $eventFilter.time = New-Object VMware.Vim.EventFilterSpecByTime + if($Start){ + $eventFilter.time.beginTime = $Start + } + if($Finish){ + $eventFilter.time.endTime = $Finish + } + } + if($User -or $System){ + $eventFilter.UserName = New-Object VMware.Vim.EventFilterSpecByUsername + if($User){ + $eventFilter.UserName.userList = $User + } + if($System){ + $eventFilter.UserName.systemUser = $System + } + } + if($ScheduledTask){ + $si = Get-View ServiceInstance + $schTskMgr = Get-View $si.Content.ScheduledTaskManager + $eventFilter.ScheduledTask = Get-View $schTskMgr.ScheduledTask | + where {$_.Info.Name -match $ScheduledTask} | + Select -First 1 | + Select -ExpandProperty MoRef + } + if(!$Entity){ + $Entity = @(Get-Folder -Name Datacenters) + } + $entity | %{ + $eventFilter.entity.entity = $_.ExtensionData.MoRef + $eventCollector = Get-View ($eventMgr.CreateCollectorForEvents($eventFilter)) + $eventsBuffer = $eventCollector.ReadNextEvents($eventnumber) + while($eventsBuffer){ + $events += $eventsBuffer + $eventsBuffer = $eventCollector.ReadNextEvents($eventnumber) + } + $eventCollector.DestroyCollector() + } + $events + } + } + +#Run parameters - Change below if username or vcenter list source changes +$dayBtwnRuns = 1 +$AdminName = "username" +$credfile = "c:\Scripts\common\credentials\runtime-cred.txt" +$vcfile = "c:\Scripts\Common\inputlists\vcenterlist.txt" + +$vmCreationTypes = @() #Remark out any event types not desired below +$vmCreationTypes += "VmCreatedEvent" +$vmCreationTypes += "VmBeingClonedEvent" +$vmCreationTypes += "VmBeingDeployedEvent" +$vmCreationTypes += "VmRegisteredEvent" +$newline = "`r`n" + +#Convert Password and username to credential object +$password = Get-Content $CredFile | ConvertTo-SecureString +$Cred = New-Object -Typename System.Management.Automation.PSCredential -argumentlist $AdminName,$password + +#Load vCenter List +$vCenterServers = Get-Content $vcfile + +If ($daysBtwnRuns -gt 0) {$daysBtwnRuns = -$daysBtwnRuns} +$Today = Get-Date +$StartDate = ($Today).AddDays($dayBtwnRuns) + +ForEach ($vcenter in $vCenterServers){ + Connect-VIServer $vcenter -Credential $Cred -WarningAction SilentlyContinue -ErrorAction SilentlyContinue + $TargetVM = $null + $VIEvent = @() + $Today = Get-Date + $StartDate = ($Today).AddDays($dayBtwnRuns) + $VIEvent = Get-VIEventPlus -Start $StartDate -Finish $Today -EventType $vmCreationTypes + + $VIEvent|%{ + $NewNote = "" + $VM = $null + $VM = Get-View -Id $_.VM.Vm -Server $vcenter -Property Name,Config + + If ($VM){ + $NewNote = $VM.Config.GuestFullName+$newline + $NewNote += "Deployed: "+$_.CreatedTime.ToLocaltime().DateTime+$newline + $NewNote += "Deployed by "+$_.UserName+$newline + $NewNote += $_.FullFormattedMessage + Set-VM -VM $VM.Name -Notes $NewNote -Confirm:$false + } + } + +Disconnect-VIServer -Confirm:$false} From e63f4178ab1532ae0127859441232bee53b7a0a6 Mon Sep 17 00:00:00 2001 From: mycloudrevolution Date: Thu, 31 Aug 2017 14:58:04 +0200 Subject: [PATCH 08/23] Add New Edge Gateway Abbility to add a Edge Gateway --- Modules/VMware-vCD-Module/README.md | 23 +- .../VMware-vCD-Module/VMware-vCD-Module.psd1 | 7 +- .../examples/OnBoarding.json | 8 +- .../functions/Invoke-MyOnBoarding.psm1 | 363 +++++++++--------- .../functions/New-MyEdgeGateway.psm1 | 161 ++++++++ .../functions/New-MyOrgVdc.psm1 | 12 +- .../media/Invoke-MyOnBoarding.png | Bin 40988 -> 90987 bytes 7 files changed, 389 insertions(+), 185 deletions(-) create mode 100644 Modules/VMware-vCD-Module/functions/New-MyEdgeGateway.psm1 diff --git a/Modules/VMware-vCD-Module/README.md b/Modules/VMware-vCD-Module/README.md index a691421..3d7bc48 100644 --- a/Modules/VMware-vCD-Module/README.md +++ b/Modules/VMware-vCD-Module/README.md @@ -9,15 +9,32 @@ VMware-vCD-Module PowerShell Module Markus Kraus [@vMarkus_K](https://twitter.com/vMarkus_K) +MY CLOUD-(R)EVOLUTION [mycloudrevolution.com](http://mycloudrevolution.com/) + + ## Project WebSite: -[mycloudrevolution.com](http://mycloudrevolution.com/) +[PowerCLI vCloud Director Customer Provisioning](https://mycloudrevolution.com/2017/06/13/powercli-vcloud-director-customer-provisioning/) + +[PowerCLI – Create vCloud Director Edge Gateway](https://mycloudrevolution.com/2017/06/27/powercli-create-vcloud-director-edge-gateway/) + ## Project Documentation: -[Read the Docs](http://vmware-vcd-module.readthedocs.io/) +[Read the Docs - VMware-vCD-Module](http://vmware-vcd-module.readthedocs.io/) ## Project Description: -The 'VMware-vCD-Module' PowerShell Module is focused on the initial craation of VMware vCloud Director Objects like Org, Org User, Org VDC. +The 'VMware-vCD-Module' PowerShell Module is focused on the initial creation of VMware vCloud Director Objects like Org, Org User, Org VDC with External Networks or Edge Gateway. + +All Functions in this Module can be used as standalone Cmdlet but also the ``Invoke-My OnBoarding`` Functions to process a JSON File and create all Objects at once. + +### Fully tested Versions: + +Powershell: v4, v5 + +PowerCLI: 6.5.1 + +VMware vCloud Director: 8.10.1 + diff --git a/Modules/VMware-vCD-Module/VMware-vCD-Module.psd1 b/Modules/VMware-vCD-Module/VMware-vCD-Module.psd1 index 59a02de..27584d1 100644 --- a/Modules/VMware-vCD-Module/VMware-vCD-Module.psd1 +++ b/Modules/VMware-vCD-Module/VMware-vCD-Module.psd1 @@ -12,7 +12,7 @@ # RootModule = '' # Die Versionsnummer dieses Moduls -ModuleVersion = '0.2.0' +ModuleVersion = '1.0.0' # ID zur eindeutigen Kennzeichnung dieses Moduls GUID = '1ef8a2de-ca22-4c88-8cdb-e00f35007d2a' @@ -21,7 +21,7 @@ GUID = '1ef8a2de-ca22-4c88-8cdb-e00f35007d2a' Author = 'Markus' # Unternehmen oder Hersteller dieses Moduls -CompanyName = 'Unbekannt' +CompanyName = 'mycloudrevolution.com' # Urheberrechtserklärung für dieses Modul Copyright = '(c) 2017 Markus. Alle Rechte vorbehalten.' @@ -64,12 +64,13 @@ Copyright = '(c) 2017 Markus. Alle Rechte vorbehalten.' # Die Module, die als geschachtelte Module des in "RootModule/ModuleToProcess" angegebenen Moduls importiert werden sollen. NestedModules = @('functions\Invoke-MyOnBoarding.psm1', + 'functions\New-MyEdgeGateway.psm1', 'functions\New-MyOrg.psm1', 'functions\New-MyOrgAdmin.psm1', 'functions\New-MyOrgVdc.psm1') # Aus diesem Modul zu exportierende Funktionen -FunctionsToExport = 'Invoke-MyOnBoarding', 'New-MyOrg', 'New-MyOrgAdmin', 'New-MyOrgVdc' +FunctionsToExport = 'Invoke-MyOnBoarding', 'New-MyEdgeGateway', 'New-MyOrg', 'New-MyOrgAdmin', 'New-MyOrgVdc' # Aus diesem Modul zu exportierende Cmdlets CmdletsToExport = '*' diff --git a/Modules/VMware-vCD-Module/examples/OnBoarding.json b/Modules/VMware-vCD-Module/examples/OnBoarding.json index ecd1f88..9fe4217 100644 --- a/Modules/VMware-vCD-Module/examples/OnBoarding.json +++ b/Modules/VMware-vCD-Module/examples/OnBoarding.json @@ -19,6 +19,12 @@ "StorageProfile":"Standard-DC01", "ProviderVDC":"Provider-VDC-DC01", "NetworkPool":"Provider-VDC-DC01-NetPool", - "ExternalNetwork": "External-OrgVdcNet" + "ExternalNetwork": "External-OrgVdcNet", + "EdgeGateway": "Yes", + "IPAddress":"192.168.100.1", + "SubnetMask":"255.255.255.0", + "Gateway":"192.168.100.254", + "IPRangeStart":"192.168.100.2", + "IPRangeEnd":"192.168.100.3" } } \ No newline at end of file diff --git a/Modules/VMware-vCD-Module/functions/Invoke-MyOnBoarding.psm1 b/Modules/VMware-vCD-Module/functions/Invoke-MyOnBoarding.psm1 index 4b5e209..97e00ed 100644 --- a/Modules/VMware-vCD-Module/functions/Invoke-MyOnBoarding.psm1 +++ b/Modules/VMware-vCD-Module/functions/Invoke-MyOnBoarding.psm1 @@ -1,172 +1,191 @@ -#Requires -Version 4 -#Requires -Modules VMware.VimAutomation.Cloud, @{ModuleName="VMware.VimAutomation.Cloud";ModuleVersion="6.3.0.0"} -Function Invoke-MyOnBoarding { -<# -.SYNOPSIS - Creates all vCD Objecst for a new IAAS Customer - -.DESCRIPTION - Creates all vCD Objects for a new IAAS Customer - - All Objects are: - * Org - * Default Org Admin - * Org VDC - ** Private Catalog - ** Optional Bridged Network - - JSON Config Example: - - { - "Org": { - "Name":"TestOrg", - "FullName": "Test Org", - "Description":"Automation Test Org" - }, - "OrgAdmin": { - "Name":"TestOrgAdmin", - "Pasword": "myPassword1!", - "FullName":"Test OrgAdmin", - "EmailAddress":"test@admin.org" - }, - "OrgVdc": { - "Name":"TestOrgVdc", - "FixedSize": "M", - "CPULimit": "1000", - "MEMLimit":"1000", - "StorageLimit":"1000", - "StorageProfile":"Standard-DC01", - "ProviderVDC":"Provider-VDC-DC01", - "NetworkPool":"Provider-VDC-DC01-NetPool", - "ExternalNetwork": "External_OrgVdcNet" - } - } - -.NOTES - File Name : Invoke-MyOnBoarding.ps1 - Author : Markus Kraus - Version : 1.2 - State : Ready - -.LINK - https://mycloudrevolution.com/ - -.EXAMPLE - Invoke-MyOnBoarding -ConfigFile ".\OnBoarding.json" -Enabled:$true - -.EXAMPLE - Invoke-MyOnBoarding -ConfigFile ".\OnBoarding.json" -Enabled:$false - -.PARAMETER ConfigFile - Full Path to the JSON Config File - -.PARAMETER Enabled - Should the Customer be enabled after creation - - Default: $False - -#> - Param ( - [Parameter(Mandatory=$True, ValueFromPipeline=$False, HelpMessage="Full Path to the JSON Config File")] - [ValidateNotNullorEmpty()] - [String] $ConfigFile, - [Parameter(Mandatory=$False, ValueFromPipeline=$False, HelpMessage="Should the Customer be enabled after creation")] - [ValidateNotNullorEmpty()] - [Switch]$Enabled - ) - Process { - - $Valid = $true - - Write-Verbose "## Import JSON Config" - Write-Host "$(Get-Date -Format "yyyy-MM-dd HH:mm:ss") Importing JSON Config...`n" - $Configs = Get-Content -Raw -Path $ConfigFile -ErrorAction Continue | ConvertFrom-Json -ErrorAction Continue - - if (!($Configs)) { - $Valid = $false - Write-Host "$(Get-Date -Format "yyyy-MM-dd HH:mm:ss") Importing JSON Config Failed" -ForegroundColor Red - } - else { - Write-Host "$(Get-Date -Format "yyyy-MM-dd HH:mm:ss") Importing JSON Config OK" -ForegroundColor Green - } - - if ($Valid) { - try{ - Write-Verbose "## Create Org" - Write-Host "$(Get-Date -Format "yyyy-MM-dd HH:mm:ss") Creating new Org...`n" -ForegroundColor Yellow - $Trash = New-MyOrg -Name $Configs.Org.Name -FullName $Configs.Org.Fullname -Description $Configs.Org.Description -Enabled:$Enabled - Write-Host "$(Get-Date -Format "yyyy-MM-dd HH:mm:ss") Creating new Org OK" -ForegroundColor Green - Get-Org -Name $Configs.Org.Name | Select-Object Name, FullName, Enabled | Format-Table -AutoSize - } - catch { - $Valid = $false - Write-Host "$(Get-Date -Format "yyyy-MM-dd HH:mm:ss") Creating new Org Failed" -ForegroundColor Red - } - } - - if ($Valid) { - try{ - Write-Verbose "## Create OrgAdmin" - Write-Host "$(Get-Date -Format "yyyy-MM-dd HH:mm:ss") Creating new OrgAdmin...`n" -ForegroundColor Yellow - $Trash = New-MyOrgAdmin -Name $Configs.OrgAdmin.Name -Pasword $Configs.OrgAdmin.Pasword -FullName $Configs.OrgAdmin.FullName -EmailAddress $Configs.OrgAdmin.EmailAddress -Org $Configs.Org.Name -Enabled:$Enabled - Write-Host "$(Get-Date -Format "yyyy-MM-dd HH:mm:ss") Creating new OrgAdmin OK" -ForegroundColor Green - Get-CIUser -Org $Configs.Org.Name -Name $Configs.OrgAdmin.Name | Select-Object Name, FullName, Email | Format-Table -AutoSize - } - catch { - $Valid = $false - Write-Host "$(Get-Date -Format "yyyy-MM-dd HH:mm:ss") Creating new OrgAdmin Failed" -ForegroundColor Red - } - } - if ($Valid) { - try{ - Write-Verbose "## Create OrgVdc" - Write-Host "$(Get-Date -Format "yyyy-MM-dd HH:mm:ss") Creating new OrgVdc...`n" -ForegroundColor Yellow - - if ($Configs.OrgVdc.FixedSize){ - - Write-Host "Fixed Size (T-Shirt Size) '$($Configs.OrgVdc.FixedSize)' Org VDC Requested!" - - switch ($Configs.OrgVdc.FixedSize) { - M { - [String]$CPULimit = 36000 - [String]$MEMLimit = 122880 - [String]$StorageLimit = 1048576 - } - L { - [String]$CPULimit = 36000 - [String]$MEMLimit = 245760 - [String]$StorageLimit = 1048576 - } - default {throw "Invalid T-Shirt Size!"} - } - - } - else{ - Write-Host "Custom Org VDC Size Requested!" - - $CPULimit = $Configs.OrgVdc.CPULimit - $MEMLimit = $Configs.OrgVdc.MEMLimit - $StorageLimit = $Configs.OrgVdc.StorageLimit - - } - - if ($Configs.OrgVdc.ExternalNetwork){ - $Trash = New-MyOrgVdc -Name $Configs.OrgVdc.Name -CPULimit $CPULimit -MEMLimit $MEMLimit -StorageLimit $StorageLimit -Networkpool $Configs.OrgVdc.NetworkPool -StorageProfile $Configs.OrgVdc.StorageProfile -ProviderVDC $Configs.OrgVdc.ProviderVDC -ExternalNetwork $Configs.OrgVdc.ExternalNetwork -Org $Configs.Org.Name -Enabled:$Enabled - } - else { - $Trash = New-MyOrgVdc -Name $Configs.OrgVdc.Name -CPULimit $CPULimit -MEMLimit $MEMLimit -StorageLimit $StorageLimit -Networkpool $Configs.OrgVdc.NetworkPool -StorageProfile $Configs.OrgVdc.StorageProfile -ProviderVDC $Configs.OrgVdc.ProviderVDC -Org $Configs.Org.Name -Enabled:$Enabled - } - Write-Host "$(Get-Date -Format "yyyy-MM-dd HH:mm:ss") Creating new OrgVdc OK" -ForegroundColor Green - Get-OrgVdc -Org $Configs.Org.Name -Name $Configs.OrgVdc.Name | Select-Object Name, Enabled, CpuAllocationGhz, MemoryLimitGB, StorageLimitGB, AllocationModel, ThinProvisioned, UseFastProvisioning, ` - @{N="StorageProfile";E={$_.ExtensionData.VdcStorageProfiles.VdcStorageProfile.Name}}, ` - @{N='VCpuInMhz';E={$_.ExtensionData.VCpuInMhz}} | Format-Table -AutoSize - } - catch { - $Valid = $false - Write-Host "$(Get-Date -Format "yyyy-MM-dd HH:mm:ss") Creating new OrgVdc Failed" -ForegroundColor Red - } - } - - Write-Output "Overall Execution was Valid: $Valid" - } -} +#Requires -Version 4 +#Requires -Modules VMware.VimAutomation.Cloud, @{ModuleName="VMware.VimAutomation.Cloud";ModuleVersion="6.3.0.0"} +Function Invoke-MyOnBoarding { +<# +.SYNOPSIS + Creates all vCD Objecst for a new IAAS Customer + +.DESCRIPTION + Creates all vCD Objects for a new IAAS Customer + + All Objects are: + * Org + * Default Org Admin + * Org VDC + ** Private Catalog + ** Optional Bridged Network + + JSON Config Example: + + { + "Org": { + "Name":"TestOrg", + "FullName": "Test Org", + "Description":"Automation Test Org" + }, + "OrgAdmin": { + "Name":"TestOrgAdmin", + "Pasword": "myPassword1!", + "FullName":"Test OrgAdmin", + "EmailAddress":"test@admin.org" + }, + "OrgVdc": { + "Name":"TestOrgVdc", + "FixedSize": "M", + "CPULimit": "1000", + "MEMLimit":"1000", + "StorageLimit":"1000", + "StorageProfile":"Standard-DC01", + "ProviderVDC":"Provider-VDC-DC01", + "NetworkPool":"Provider-VDC-DC01-NetPool", + "ExternalNetwork": "External_OrgVdcNet", + "EdgeGateway": "Yes", + "IPAddress":"192.168.100.1", + "SubnetMask":"255.255.255.0", + "Gateway":"192.168.100.254", + "IPRangeStart":"192.168.100.2", + "IPRangeEnd":"192.168.100.3" + } + } + +.NOTES + File Name : Invoke-MyOnBoarding.ps1 + Author : Markus Kraus + Version : 1.3 + State : Ready + +.LINK + https://mycloudrevolution.com/ + +.EXAMPLE + Invoke-MyOnBoarding -ConfigFile ".\OnBoarding.json" -Enabled:$true + +.EXAMPLE + Invoke-MyOnBoarding -ConfigFile ".\OnBoarding.json" -Enabled:$false + +.PARAMETER ConfigFile + Full Path to the JSON Config File + +.PARAMETER Enabled + Should the Customer be enabled after creation + + Default: $False + +#> + Param ( + [Parameter(Mandatory=$True, ValueFromPipeline=$False, HelpMessage="Full Path to the JSON Config File")] + [ValidateNotNullorEmpty()] + [String] $ConfigFile, + [Parameter(Mandatory=$False, ValueFromPipeline=$False, HelpMessage="Should the Customer be enabled after creation")] + [ValidateNotNullorEmpty()] + [Switch]$Enabled + ) + Process { + + $Valid = $true + + Write-Verbose "## Import JSON Config" + Write-Host "$(Get-Date -Format "yyyy-MM-dd HH:mm:ss") Importing JSON Config...`n" + $Configs = Get-Content -Raw -Path $ConfigFile -ErrorAction Continue | ConvertFrom-Json -ErrorAction Continue + + if (!($Configs)) { + $Valid = $false + Write-Host "$(Get-Date -Format "yyyy-MM-dd HH:mm:ss") Importing JSON Config Failed" -ForegroundColor Red + } + else { + Write-Host "$(Get-Date -Format "yyyy-MM-dd HH:mm:ss") Importing JSON Config OK" -ForegroundColor Green + } + + if ($Valid) { + try{ + Write-Verbose "## Create Org" + Write-Host "$(Get-Date -Format "yyyy-MM-dd HH:mm:ss") Creating new Org...`n" -ForegroundColor Yellow + $Trash = New-MyOrg -Name $Configs.Org.Name -FullName $Configs.Org.Fullname -Description $Configs.Org.Description -Enabled:$Enabled + Write-Host "$(Get-Date -Format "yyyy-MM-dd HH:mm:ss") Creating new Org OK" -ForegroundColor Green + Get-Org -Name $Configs.Org.Name | Select-Object Name, FullName, Enabled | Format-Table -AutoSize + } + catch { + $Valid = $false + Write-Host "$(Get-Date -Format "yyyy-MM-dd HH:mm:ss") Creating new Org Failed" -ForegroundColor Red + } + } + + if ($Valid) { + try{ + Write-Verbose "## Create OrgAdmin" + Write-Host "$(Get-Date -Format "yyyy-MM-dd HH:mm:ss") Creating new OrgAdmin...`n" -ForegroundColor Yellow + $Trash = New-MyOrgAdmin -Name $Configs.OrgAdmin.Name -Pasword $Configs.OrgAdmin.Pasword -FullName $Configs.OrgAdmin.FullName -EmailAddress $Configs.OrgAdmin.EmailAddress -Org $Configs.Org.Name -Enabled:$Enabled + Write-Host "$(Get-Date -Format "yyyy-MM-dd HH:mm:ss") Creating new OrgAdmin OK" -ForegroundColor Green + Get-CIUser -Org $Configs.Org.Name -Name $Configs.OrgAdmin.Name | Select-Object Name, FullName, Email | Format-Table -AutoSize + } + catch { + $Valid = $false + Write-Host "$(Get-Date -Format "yyyy-MM-dd HH:mm:ss") Creating new OrgAdmin Failed" -ForegroundColor Red + } + } + if ($Valid) { + try{ + Write-Verbose "## Create OrgVdc" + Write-Host "$(Get-Date -Format "yyyy-MM-dd HH:mm:ss") Creating new OrgVdc...`n" -ForegroundColor Yellow + + if ($Configs.OrgVdc.FixedSize){ + + Write-Host "Fixed Size (T-Shirt Size) '$($Configs.OrgVdc.FixedSize)' Org VDC Requested!" + + switch ($Configs.OrgVdc.FixedSize) { + M { + [String]$CPULimit = 36000 + [String]$MEMLimit = 122880 + [String]$StorageLimit = 1048576 + } + L { + [String]$CPULimit = 36000 + [String]$MEMLimit = 245760 + [String]$StorageLimit = 1048576 + } + default {throw "Invalid T-Shirt Size!"} + } + + } + else{ + Write-Host "Custom Org VDC Size Requested!" + + $CPULimit = $Configs.OrgVdc.CPULimit + $MEMLimit = $Configs.OrgVdc.MEMLimit + $StorageLimit = $Configs.OrgVdc.StorageLimit + + } + + if ($Configs.OrgVdc.ExternalNetwork -and $Configs.OrgVdc.EdgeGateway -like "Yes"){ + Write-Host "Edge Gateway for Org VDC '$($Configs.OrgVdc.Name)' Requested!" + $Trash = New-MyOrgVdc -Name $Configs.OrgVdc.Name -CPULimit $CPULimit -MEMLimit $MEMLimit -StorageLimit $StorageLimit -Networkpool $Configs.OrgVdc.NetworkPool ` -StorageProfile $Configs.OrgVdc.StorageProfile -ProviderVDC $Configs.OrgVdc.ProviderVDC -Org $Configs.Org.Name -Enabled:$Enabled + + $EdgeName = $Configs.Org.Name + "-ESG01" + $Trash = New-MyEdgeGateway -Name $EdgeName -OrgVDCName $Configs.OrgVdc.Name -Orgname $Configs.Org.Name -ExternalNetwork $Configs.OrgVdc.ExternalNetwork ` -IPAddress $Configs.OrgVdc.IPAddress -SubnetMask $Configs.OrgVdc.SubnetMask -Gateway $Configs.OrgVdc.Gateway -IPRangeStart $Configs.OrgVdc.IPRangeStart -IPRangeEnd $Configs.OrgVdc.IPRangeEnd + } + elseif ($Configs.OrgVdc.ExternalNetwork -and $Configs.OrgVdc.EdgeGateway -like "No"){ + Write-Host "External Network for Org VDC '$($Configs.OrgVdc.Name)' Requested!" + $Trash = New-MyOrgVdc -Name $Configs.OrgVdc.Name -CPULimit $CPULimit -MEMLimit $MEMLimit -StorageLimit $StorageLimit -Networkpool $Configs.OrgVdc.NetworkPool ` -StorageProfile $Configs.OrgVdc.StorageProfile -ProviderVDC $Configs.OrgVdc.ProviderVDC -ExternalNetwork $Configs.OrgVdc.ExternalNetwork -Org $Configs.Org.Name -Enabled:$Enabled + } + else { + Write-Host "No external Connection for Org VDC '$($Configs.OrgVdc.Name)' Requested!" + $Trash = New-PecOrgVdc -Name $Configs.OrgVdc.Name -CPULimit $CPULimit -MEMLimit $MEMLimit -StorageLimit $StorageLimit -Networkpool $ProVdcNetworkPool.Name ` -StorageProfile $Configs.OrgVdc.StorageProfile -ProviderVDC $Configs.OrgVdc.ProviderVDC -Org $Configs.Org.Name -Enabled:$Enabled + } + Write-Host "$(Get-Date -Format "yyyy-MM-dd HH:mm:ss") Creating new OrgVdc OK" -ForegroundColor Green + Get-OrgVdc -Org $Configs.Org.Name -Name $Configs.OrgVdc.Name | Select-Object Name, Enabled, CpuAllocationGhz, MemoryLimitGB, StorageLimitGB, AllocationModel, ThinProvisioned, UseFastProvisioning, ` + @{N="StorageProfile";E={$_.ExtensionData.VdcStorageProfiles.VdcStorageProfile.Name}}, ` + @{N='VCpuInMhz';E={$_.ExtensionData.VCpuInMhz}} | Format-Table -AutoSize + + if ($Configs.OrgVdc.EdgeGateway -like "Yes"){ + Search-Cloud -QueryType EdgeGateway -Name $EdgeName | Select Name, IsBusy, GatewayStatus, HaStatus | ft -AutoSize + } + } + catch { + $Valid = $false + Write-Host "$(Get-Date -Format "yyyy-MM-dd HH:mm:ss") Creating new OrgVdc Failed" -ForegroundColor Red + } + } + + Write-Output "Overall Execution was Valid: $Valid" + } +} diff --git a/Modules/VMware-vCD-Module/functions/New-MyEdgeGateway.psm1 b/Modules/VMware-vCD-Module/functions/New-MyEdgeGateway.psm1 new file mode 100644 index 0000000..205c635 --- /dev/null +++ b/Modules/VMware-vCD-Module/functions/New-MyEdgeGateway.psm1 @@ -0,0 +1,161 @@ +#Requires -Version 4 +#Requires -Modules VMware.VimAutomation.Cloud, @{ModuleName="VMware.VimAutomation.Cloud";ModuleVersion="6.3.0.0"} +Function New-MyEdgeGateway { +<# +.SYNOPSIS + Creates a new Edge Gateway with Default Parameters + +.DESCRIPTION + Creates a new Edge Gateway with Default Parameters + + Default Parameters are: + * Size + * HA State + * DNS Relay + + +.NOTES + File Name : New-MyEdgeGateway.ps1 + Author : Markus Kraus + Version : 1.0 + State : Ready + +.LINK + https://mycloudrevolution.com/ + +.EXAMPLE + New-MyEdgeGateway -Name "TestEdge" -OrgVDCName "TestVDC" -OrgName "TestOrg" -ExternalNetwork "ExternalNetwork" -IPAddress "192.168.100.1" -SubnetMask "255.255.255.0" -Gateway "192.168.100.254" -IPRangeStart ""192.168.100.2" -IPRangeEnd ""192.168.100.3" -Verbose + +.PARAMETER Name + Name of the New Edge Gateway as String + +.PARAMETER OrgVDCName + OrgVDC where the new Edge Gateway should be created as string + +.PARAMETER OrgName + Org where the new Edge Gateway should be created as string + +.PARAMETER ExternalNetwork + External Network of the new Edge Gateway as String + +.PARAMETER IPAddress + IP Address of the New Edge Gateway as IP Address + +.PARAMETER SubnetMask + Subnet Mask of the New Edge Gateway as IP Address + +.PARAMETER Gateway + Gateway of the New Edge Gateway as IP Address + +.PARAMETER IPRangeStart + Sub Allocation IP Range Start of the New Edge Gateway as IP Address + +.PARAMETER IPRangeEnd + Sub Allocation IP Range End of the New Edge Gateway as IP Address + +.PARAMETER Timeout + Timeout for the Edge Gateway to get Ready + + Default: 120s + +#> + Param ( + [Parameter(Mandatory=$True, ValueFromPipeline=$False, HelpMessage="Name of the New Edge Gateway as String")] + [ValidateNotNullorEmpty()] + [String] $Name, + [Parameter(Mandatory=$True, ValueFromPipeline=$False, HelpMessage="OrgVDC where the new Edge Gateway should be created as string")] + [ValidateNotNullorEmpty()] + [String] $OrgVdcName, + [Parameter(Mandatory=$True, ValueFromPipeline=$False, HelpMessage="Org where the new Edge Gateway should be created as string")] + [ValidateNotNullorEmpty()] + [String] $OrgName, + [Parameter(Mandatory=$True, ValueFromPipeline=$False, HelpMessage="External Network of the New Edge Gateway as String")] + [ValidateNotNullorEmpty()] + [String] $ExternalNetwork, + [Parameter(Mandatory=$True, ValueFromPipeline=$False, HelpMessage="IP Address of the New Edge Gateway as IP Address")] + [ValidateNotNullorEmpty()] + [IPAddress] $IPAddress, + [Parameter(Mandatory=$True, ValueFromPipeline=$False, HelpMessage="Subnet Mask of the New Edge Gateway as IP Address")] + [ValidateNotNullorEmpty()] + [IPAddress] $SubnetMask, + [Parameter(Mandatory=$True, ValueFromPipeline=$False, HelpMessage="Gateway of the New Edge Gateway as IP Address")] + [ValidateNotNullorEmpty()] + [IPAddress] $Gateway, + [Parameter(Mandatory=$True, ValueFromPipeline=$False, HelpMessage="Sub Allocation IP Range Start the New Edge Gateway as IP Address")] + [ValidateNotNullorEmpty()] + [IPAddress] $IPRangeStart, + [Parameter(Mandatory=$True, ValueFromPipeline=$False, HelpMessage="Sub Allocation IP Range End the New Edge Gateway as IP Address")] + [ValidateNotNullorEmpty()] + [IPAddress] $IPRangeEnd, + [Parameter(Mandatory=$False, ValueFromPipeline=$False,HelpMessage="Timeout for the Edge Gateway to get Ready")] + [ValidateNotNullorEmpty()] + [int] $Timeout = 120 + ) + Process { + + ## Get Org vDC + Write-Verbose "Get Org vDC" + [Array] $orgVdc = Get-Org -Name $OrgName | Get-OrgVdc -Name $OrgVdcName + + if ( $orgVdc.Count -gt 1) { + throw "Multiple OrgVdcs found!" + } + elseif ( $orgVdc.Count -lt 1) { + throw "No OrgVdc found!" + } + ## Get External Network + Write-Verbose "Get External Network" + $extNetwork = Get-ExternalNetwork | Get-CIView -Verbose:$False | Where-Object {$_.name -eq $ExternalNetwork} + + ## Build EdgeGatway Configuration + Write-Verbose "Build EdgeGatway Configuration" + $EdgeGateway = New-Object VMware.VimAutomation.Cloud.Views.Gateway + $EdgeGateway.Name = $Name + $EdgeGateway.Configuration = New-Object VMware.VimAutomation.Cloud.Views.GatewayConfiguration + #$EdgeGateway.Configuration.BackwardCompatibilityMode = $false + $EdgeGateway.Configuration.GatewayBackingConfig = "compact" + $EdgeGateway.Configuration.UseDefaultRouteForDnsRelay = $false + $EdgeGateway.Configuration.HaEnabled = $false + + $EdgeGateway.Configuration.EdgeGatewayServiceConfiguration = New-Object VMware.VimAutomation.Cloud.Views.GatewayFeatures + $EdgeGateway.Configuration.GatewayInterfaces = New-Object VMware.VimAutomation.Cloud.Views.GatewayInterfaces + + $EdgeGateway.Configuration.GatewayInterfaces.GatewayInterface = New-Object VMware.VimAutomation.Cloud.Views.GatewayInterface + $EdgeGateway.Configuration.GatewayInterfaces.GatewayInterface[0].name = $extNetwork.Name + $EdgeGateway.Configuration.GatewayInterfaces.GatewayInterface[0].DisplayName = $extNetwork.Name + $EdgeGateway.Configuration.GatewayInterfaces.GatewayInterface[0].Network = $extNetwork.Href + $EdgeGateway.Configuration.GatewayInterfaces.GatewayInterface[0].InterfaceType = "uplink" + $EdgeGateway.Configuration.GatewayInterfaces.GatewayInterface[0].UseForDefaultRoute = $true + $EdgeGateway.Configuration.GatewayInterfaces.GatewayInterface[0].ApplyRateLimit = $false + + $ExNetexternalSubnet = New-Object VMware.VimAutomation.Cloud.Views.SubnetParticipation + $ExNetexternalSubnet.Gateway = $Gateway.IPAddressToString + $ExNetexternalSubnet.Netmask = $SubnetMask.IPAddressToString + $ExNetexternalSubnet.IpAddress = $IPAddress.IPAddressToString + $ExNetexternalSubnet.IpRanges = New-Object VMware.VimAutomation.Cloud.Views.IpRanges + $ExNetexternalSubnet.IpRanges.IpRange = New-Object VMware.VimAutomation.Cloud.Views.IpRange + $ExNetexternalSubnet.IpRanges.IpRange[0].StartAddress = $IPRangeStart.IPAddressToString + $ExNetexternalSubnet.IpRanges.IpRange[0].EndAddress = $IPRangeEnd.IPAddressToString + + $EdgeGateway.Configuration.GatewayInterfaces.GatewayInterface[0].SubnetParticipation = $ExNetexternalSubnet + + ## Create EdgeGatway + Write-Verbose "Create EdgeGatway" + $CreateEdgeGateway = $orgVdc.ExtensionData.CreateEdgeGateway($EdgeGateway) + + ## Wait for EdgeGatway to become Ready + Write-Verbose "Wait for EdgeGatway to become Ready" + while((Search-Cloud -QueryType EdgeGateway -Name $Name -Verbose:$False).IsBusy -eq $True){ + $i++ + Start-Sleep 5 + if($i -gt $Timeout) { Write-Error "Creating Edge Gateway."; break} + Write-Progress -Activity "Creating Edge Gateway" -Status "Wait for Edge to become Ready..." + } + Write-Progress -Activity "Creating Edge Gateway" -Completed + Start-Sleep 1 + + Search-Cloud -QueryType EdgeGateway -Name $Name | Select-Object Name, IsBusy, GatewayStatus, HaStatus | Format-Table -AutoSize + + + } +} diff --git a/Modules/VMware-vCD-Module/functions/New-MyOrgVdc.psm1 b/Modules/VMware-vCD-Module/functions/New-MyOrgVdc.psm1 index 783ad5e..26258e7 100644 --- a/Modules/VMware-vCD-Module/functions/New-MyOrgVdc.psm1 +++ b/Modules/VMware-vCD-Module/functions/New-MyOrgVdc.psm1 @@ -67,7 +67,7 @@ Function New-MyOrgVdc { Org where the new Org VDC should be created as string .PARAMETER Timeout - Timeout for teh Org VDC to get Ready + Timeout for the Org VDC to get Ready Default: 120s @@ -103,7 +103,7 @@ Function New-MyOrgVdc { [Parameter(Mandatory=$True, ValueFromPipeline=$False, HelpMessage="Org where the new Org VDC should be created as string")] [ValidateNotNullorEmpty()] [String] $Org, - [Parameter(Mandatory=$False, ValueFromPipeline=$False,HelpMessage="Timeout for teh Org VDC to get Ready")] + [Parameter(Mandatory=$False, ValueFromPipeline=$False,HelpMessage="Timeout for the Org VDC to get Ready")] [ValidateNotNullorEmpty()] [int] $Timeout = 120 ) @@ -145,7 +145,7 @@ Function New-MyOrgVdc { ## Wait for getting Ready Write-Verbose "Wait for getting Ready" $i = 0 - while(($orgVdc = Get-OrgVdc -Name $Name).Status -eq "NotReady"){ + while(($orgVdc = Get-OrgVdc -Name $Name -Verbose:$false).Status -eq "NotReady"){ $i++ Start-Sleep 2 if($i -gt $Timeout) { Write-Error "Creating Org Failed."; break} @@ -175,7 +175,7 @@ Function New-MyOrgVdc { ## Wait for getting Ready Write-Verbose "Wait for getting Ready" - while(($orgVdc = Get-OrgVdc -Name $name).Status -eq "NotReady"){ + while(($orgVdc = Get-OrgVdc -Name $name -Verbose:$false).Status -eq "NotReady"){ $i++ Start-Sleep 1 if($i -gt $Timeout) { Write-Error "Update Org Failed."; break} @@ -201,7 +201,7 @@ Function New-MyOrgVdc { ## Wait for getting Ready Write-Verbose "Wait for getting Ready" - while(($orgVdc = Get-OrgVdc -Name $name).Status -eq "NotReady"){ + while(($orgVdc = Get-OrgVdc -Name $name -Verbose:$false).Status -eq "NotReady"){ $i++ Start-Sleep 1 if($i -gt $Timeout) { Write-Error "Update Org Failed."; break} @@ -236,7 +236,7 @@ Function New-MyOrgVdc { $EnableOrgVdc = Set-OrgVdc -OrgVdc $Name -Enabled:$True $orgVdcView = Get-OrgVdc $Name | Get-CIView $extNetwork = $_.externalnetwork - $extNetwork = Get-ExternalNetwork | Get-CIView | Where-Object {$_.name -eq $ExternalNetwork} + $extNetwork = Get-ExternalNetwork | Get-CIView -Verbose:$false | Where-Object {$_.name -eq $ExternalNetwork} $orgNetwork = new-object vmware.vimautomation.cloud.views.orgvdcnetwork $orgNetwork.name = $ExternalNetwork $orgNetwork.Configuration = New-Object VMware.VimAutomation.Cloud.Views.NetworkConfiguration diff --git a/Modules/VMware-vCD-Module/media/Invoke-MyOnBoarding.png b/Modules/VMware-vCD-Module/media/Invoke-MyOnBoarding.png index 9f60a93508c54e47418fa493ab0b730f8b003a3f..c09f0fe830d30617944f0edefc64716c0ec8ca60 100644 GIT binary patch literal 90987 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 HVETUmkdju5 literal 40988 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 %IBM`Y0F&%$%@Mz>h~Xp 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 Y0@TidQEK`NO#t%qyOtiTT*EfvACt(Yp#T5? From fc90f81d88d29d2d04b5c10fef3438d5342aa50f Mon Sep 17 00:00:00 2001 From: haberstrohr Date: Thu, 31 Aug 2017 10:02:54 -0500 Subject: [PATCH 09/23] Get-VMHostWWPN Returns WWPN in Base16 format, similar to what is displayed in the GUI. --- Scripts/Get-VMHostWWPN.ps1 | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 Scripts/Get-VMHostWWPN.ps1 diff --git a/Scripts/Get-VMHostWWPN.ps1 b/Scripts/Get-VMHostWWPN.ps1 new file mode 100644 index 0000000..921b5bb --- /dev/null +++ b/Scripts/Get-VMHostWWPN.ps1 @@ -0,0 +1,17 @@ +function Get-VMHostWWPN { +<# +Script name: Get-VMHostWWPN.ps1 +Created on: 08/31/2017 +Author: Robin Haberstroh, @strohland +Description: This script returns the WWPN of the hosts FiberChannel HBA in a readable format that corresponds to what storage team expects +Dependencies: None known +#> + param( + [string]$cluster + ) + + Get-Cluster $cluster | get-vmhost | get-vmhosthba -type FibreChannel | + format-table VMHost, Device, @{ + n='WorldWidePortName';e={[convert]::ToString($_.PortWorldWideName, 16)} + } +} \ No newline at end of file From 1ed68eeab2f4d2022e3caf37e971792037d448f3 Mon Sep 17 00:00:00 2001 From: mycloudrevolution Date: Fri, 1 Sep 2017 15:45:18 +0200 Subject: [PATCH 10/23] New Module VMware-vCD-TenantReport This Module Creates a nice HTML Report for vCD Customers. Report Contains Objects like Users, Catalogs, VDCs, vApps, VMs, Edge Gateways --- Modules/VMware-vCD-TenantReport/README.md | 30 +++ .../VMware-vCD-TenantReport.psd1 | 122 +++++++++ .../functions/Get-VcdTenantReport.psm1 | 251 ++++++++++++++++++ .../media/Get-VcdTenantReport.png | Bin 0 -> 6197 bytes .../PowerStartHTML/PowerStartHTML.psd1 | Bin 0 -> 7874 bytes .../PowerStartHTML/PowerStartHTML.psm1 | 237 +++++++++++++++++ .../tests/VMware-vCD-TenantReport.Tests.ps1 | 24 ++ 7 files changed, 664 insertions(+) create mode 100644 Modules/VMware-vCD-TenantReport/README.md create mode 100644 Modules/VMware-vCD-TenantReport/VMware-vCD-TenantReport.psd1 create mode 100644 Modules/VMware-vCD-TenantReport/functions/Get-VcdTenantReport.psm1 create mode 100644 Modules/VMware-vCD-TenantReport/media/Get-VcdTenantReport.png create mode 100644 Modules/VMware-vCD-TenantReport/modules/PowerStartHTML/PowerStartHTML.psd1 create mode 100644 Modules/VMware-vCD-TenantReport/modules/PowerStartHTML/PowerStartHTML.psm1 create mode 100644 Modules/VMware-vCD-TenantReport/tests/VMware-vCD-TenantReport.Tests.ps1 diff --git a/Modules/VMware-vCD-TenantReport/README.md b/Modules/VMware-vCD-TenantReport/README.md new file mode 100644 index 0000000..ac9b9f8 --- /dev/null +++ b/Modules/VMware-vCD-TenantReport/README.md @@ -0,0 +1,30 @@ +VMware-vCD-TenantReport PowerShell Module +============= + +# About + +## Project Owner: + +Markus Kraus [@vMarkus_K](https://twitter.com/vMarkus_K) + +MY CLOUD-(R)EVOLUTION [mycloudrevolution.com](http://mycloudrevolution.com/) + +## Project WebSite: + +[mycloudrevolution.com](http://mycloudrevolution.com/) + +## Project Documentation: + +[Read the Docs](http://readthedocs.io/) + +## Project Description: + +The 'VMware-vCD-TenantReport' PowerShell Module creates with the Fuction 'Get-VcdTenantReport' a HTML Report of your vCloud Director Objects. + +![Get-VcdTenantReport](/media/Get-VcdTenantReport.png) + +Big thanks to [Timothy Dewin](https://twitter.com/tdewin) for his great [PowerStartHTML](https://github.com/tdewin/randomsamples/tree/master/powerstarthtml) PowerShell Module which is used to generate the Report for this Module. + + + + diff --git a/Modules/VMware-vCD-TenantReport/VMware-vCD-TenantReport.psd1 b/Modules/VMware-vCD-TenantReport/VMware-vCD-TenantReport.psd1 new file mode 100644 index 0000000..32afbb5 --- /dev/null +++ b/Modules/VMware-vCD-TenantReport/VMware-vCD-TenantReport.psd1 @@ -0,0 +1,122 @@ +# +# Modulmanifest für das Modul "VMware-vCD-TenantReport" +# +# Generiert von: Markus Kraus +# +# Generiert am: 8/22/2017 +# + +@{ + +# Die diesem Manifest zugeordnete Skript- oder Binärmoduldatei. +# RootModule = 'VMware-vCD-TenantReport.psm1' + +# Die Versionsnummer dieses Moduls +ModuleVersion = '1.0.2' + +# ID zur eindeutigen Kennzeichnung dieses Moduls +GUID = '21a71eaa-d259-48c5-8482-643ba152af76' + +# Autor dieses Moduls +Author = 'Markus' + +# Unternehmen oder Hersteller dieses Moduls +CompanyName = 'mycloudrevolution.com' + +# Urheberrechtserklärung für dieses Modul +Copyright = '(c) 2017 Markus Kraus. Alle Rechte vorbehalten.' + +# Beschreibung der von diesem Modul bereitgestellten Funktionen +# Description = '' + +# Die für dieses Modul mindestens erforderliche Version des Windows PowerShell-Moduls +# PowerShellVersion = '' + +# Der Name des für dieses Modul erforderlichen Windows PowerShell-Hosts +# PowerShellHostName = '' + +# Die für dieses Modul mindestens erforderliche Version des Windows PowerShell-Hosts +# PowerShellHostVersion = '' + +# Die für dieses Modul mindestens erforderliche Microsoft .NET Framework-Version +# DotNetFrameworkVersion = '' + +# Die für dieses Modul mindestens erforderliche Version der CLR (Common Language Runtime) +# CLRVersion = '' + +# Die für dieses Modul erforderliche Prozessorarchitektur ("Keine", "X86", "Amd64"). +# ProcessorArchitecture = '' + +# Die Module, die vor dem Importieren dieses Moduls in die globale Umgebung geladen werden müssen +# RequiredModules = @() + +# Die Assemblys, die vor dem Importieren dieses Moduls geladen werden müssen +# RequiredAssemblies = @() + +# Die Skriptdateien (PS1-Dateien), die vor dem Importieren dieses Moduls in der Umgebung des Aufrufers ausgeführt werden. +# ScriptsToProcess = @() + +# Die Typdateien (.ps1xml), die beim Importieren dieses Moduls geladen werden sollen +# TypesToProcess = @() + +# Die Formatdateien (.ps1xml), die beim Importieren dieses Moduls geladen werden sollen +# FormatsToProcess = @() + +# Die Module, die als geschachtelte Module des in "RootModule/ModuleToProcess" angegebenen Moduls importiert werden sollen. +NestedModules = @( "modules/PowerStartHTML/PowerStartHTML.psd1", + "functions/Get-VcdTenantReport.psm1" ) + +# Aus diesem Modul zu exportierende Funktionen +FunctionsToExport = '*' + +# Aus diesem Modul zu exportierende Cmdlets +CmdletsToExport = '*' + +# Die aus diesem Modul zu exportierenden Variablen +VariablesToExport = '*' + +# Aus diesem Modul zu exportierende Aliase +AliasesToExport = '*' + +# Aus diesem Modul zu exportierende DSC-Ressourcen +# DscResourcesToExport = @() + +# Liste aller Module in diesem Modulpaket +# ModuleList = @() + +# Liste aller Dateien in diesem Modulpaket +# FileList = @() + +# Die privaten Daten, die an das in "RootModule/ModuleToProcess" angegebene Modul übergeben werden sollen. Diese können auch eine PSData-Hashtabelle mit zusätzlichen von PowerShell verwendeten Modulmetadaten enthalten. +PrivateData = @{ + + PSData = @{ + + # 'Tags' wurde auf das Modul angewendet und unterstützt die Modulermittlung in Onlinekatalogen. + # Tags = @() + + # Eine URL zur Lizenz für dieses Modul. + # LicenseUri = '' + + # Eine URL zur Hauptwebsite für dieses Projekt. + # ProjectUri = '' + + # Eine URL zu einem Symbol, das das Modul darstellt. + # IconUri = '' + + # 'ReleaseNotes' des Moduls + # ReleaseNotes = '' + + } # Ende der PSData-Hashtabelle + +} # Ende der PrivateData-Hashtabelle + +# HelpInfo-URI dieses Moduls +# HelpInfoURI = '' + +# Standardpräfix für Befehle, die aus diesem Modul exportiert werden. Das Standardpräfix kann mit "Import-Module -Prefix" überschrieben werden. +# DefaultCommandPrefix = '' + +} + + diff --git a/Modules/VMware-vCD-TenantReport/functions/Get-VcdTenantReport.psm1 b/Modules/VMware-vCD-TenantReport/functions/Get-VcdTenantReport.psm1 new file mode 100644 index 0000000..ee1296e --- /dev/null +++ b/Modules/VMware-vCD-TenantReport/functions/Get-VcdTenantReport.psm1 @@ -0,0 +1,251 @@ +function Get-VcdTenantReport { +<# + .NOTES + =========================================================================== + Created by: Markus Kraus + Twitter: @VMarkus_K + Private Blog: mycloudrevolution.com + =========================================================================== + Changelog: + 1.0.0 - Inital Release + 1.0.1 - Removed "Test-IP" Module + 1.0.2 - More Detailed Console Log + =========================================================================== + External Code Sources: + Examle Usage of BOOTSTRAP with PowerShell + https://github.com/tdewin/randomsamples/tree/master/powershell-veeamallstat + BOOTSTRAP with PowerShell + https://github.com/tdewin/randomsamples/tree/master/powerstarthtml + =========================================================================== + Tested Against Environment: + vCD Version: 8.20 + PowerCLI Version: PowerCLI 6.5.1 + PowerShell Version: 5.0 + OS Version: Windows 8.1 + Keyword: VMware, vCD, Report, HTML + =========================================================================== + + .DESCRIPTION + This Function creates a HTML Report for your vCloud Director Organization. + + This Function is fully tested as Organization Administrator. + With lower permissions a unexpected behavior is possible. + + .Example + Get-VcdTenantReport -Server $ServerFQDN -Org $OrgName -Credential $MyCedential + + .Example + Get-VcdTenantReport -Server $ServerFQDN -Org $OrgName -Path "C:\Temp\Report.html" + + .PARAMETER Server + The FQDN of your vCloud Director Endpoint. + + .PARAMETER Org + The Organization Name. + + .PARAMETER Credential + PowerShell Credentials to access the Eénvironment. + + .PARAMETER Path + The Path of the exported HTML Report. + +#> +#Requires -Version 5 +#Requires -Modules VMware.VimAutomation.Cloud, @{ModuleName="VMware.VimAutomation.Cloud";ModuleVersion="6.5.1.0"} + +[CmdletBinding()] +param( + [Parameter(Mandatory=$True, ValueFromPipeline=$False)] + [ValidateNotNullorEmpty()] + [String] $Server, + [Parameter(Mandatory=$True, ValueFromPipeline=$False)] + [ValidateNotNullorEmpty()] + [String] $Org, + [Parameter(Mandatory=$False, ValueFromPipeline=$False)] + [ValidateNotNullorEmpty()] + [PSCredential] $Credential, + [Parameter(Mandatory=$false, ValueFromPipeline=$False)] + [ValidateNotNullorEmpty()] + [String] $Path = ".\Report.html" + +) + +Process { + + # Start Connection to vCD + + if ($global:DefaultCIServers) { + "$(Get-Date -Format "yyyy-MM-dd HH:mm:ss") - Disconnect existing vCD Server ..." + $Trash = Disconnect-CIServer -Server * -Force:$true -Confirm:$false -ErrorAction SilentlyContinue + } + + "$(Get-Date -Format "yyyy-MM-dd HH:mm:ss") - Connect vCD Server ..." + if ($Credential) { + $Trash = Connect-CIServer -Server $Server -Org $Org -Credential $Credential -ErrorAction Stop + } + else { + $Trash = Connect-CIServer -Server $Server -Org $Org -ErrorAction Stop + } + "$(Get-Date -Format "yyyy-MM-dd HH:mm:ss") - Create HTML Report..." + + # Init HTML Report + $ps = New-PowerStartHTML -title "vCD Tenant Report" + + #Set CSS Style + $ps.cssStyles['.bgtitle'] = "background-color:grey" + $ps.cssStyles['.bgsubsection'] = "background-color:#eee;" + + # Processing Data + ## Get Main Objects + [Array] $OrgVdcs = Get-OrgVdc + [Array] $Catalogs = Get-Catalog + [Array] $Users = Get-CIUser + + ## Add Header to Report + $ps.Main().Add("div","jumbotron").N() + $ps.Append("h1","display-3",("vCD Tenant Report" -f $OrgVdcs.Count)).Append("p","lead","Organization User Count: {0}" -f $Users.Count).Append("p","lead","Organization Catalog Count: {0}" -f $Catalogs.Count).Append("p","lead","Organization VDC Count: {0}" -f $OrgVdcs.Count).Append("hr","my-4").Append("p","font-italic","This Report lists the most important objects in your vCD Environmet. For more details contact your Service Provider").N() + + ## add Org Users to Report + $ps.Main().Append("h2",$null,"Org Users").N() + + $ps.Add('table','table').Add("tr","bgtitle text-white").Append("th",$null,"User Name").Append("th",$null,"Locked").Append("th",$null,"DeployedVMCount").Append("th",$null,"StoredVMCount").N() + $ps.Add("tr").N() + + foreach ($User in $Users) { + $ps.Append("td",$null,$User.Name).N() + $ps.Append("td",$null,$User.Locked).N() + $ps.Append("td",$null,$User.DeployedVMCount).N() + $ps.Append("td",$null,$User.StoredVMCount).N() + $ps.Up().N() + + } + $ps.Up().N() + + ## add Org Catalogs to Report + $ps.Main().Append("h2",$null,"Org Catalogs").N() + + foreach ($Catalog in $Catalogs) { + $ps.Add('table','table').Add("tr","bgtitle text-white").Append("th",$null,"Catalog Name").N() + $ps.Add("tr").N() + $ps.Append("td",$null,$Catalog.Name).Up().N() + + $ps.Add("td","bgsubsection").N() + $ps.Add("table","table bgcolorsub").N() + $ps.Add("tr").N() + + $headers = @("Item") + foreach ($h in $headers) { + $ps.Append("th",$null,$h).N() + } + $ps.Up().N() + + ### add Itens of the Catalog to the Report + [Array] $Items = $Catalog.ExtensionData.CatalogItems.CatalogItem + + foreach ($Item in $Items) { + $ps.Add("tr").N() + $ps.Append("td",$null,$Item.Name).N() + + $ps.Up().N() + + } + + $ps.Up().Up().N() + } + $ps.Up().N() + + ## add Org VDC`s to the Report + $ps.Main().Append("h2",$null,"Org VDCs").N() + + foreach ($OrgVdc in $OrgVdcs) { + $ps.Main().Add('table','table table-striped table-inverse').Add("tr").Append("th",$null,"VDC Name").Append("th",$null,"Enabled").Append("th",$null,"CpuUsedGHz").Append("th",$null,"MemoryUsedGB").Append("th",$null,"StorageUsedGB").Up().N() + $ps.Add("tr").N() + $ps.Append("td",$null,$OrgVdc.Name).Append("td",$null,$OrgVdc.Enabled).Append("td",$null,$OrgVdc.CpuUsedGHz).Append("td",$null,$OrgVdc.MemoryUsedGB).Append("td",$null,[Math]::Round($OrgVdc.StorageUsedGB,2)).Up().N() + + ### add Edge Gateways of this Org VDC to Report + $ps.Main().Append("h3",$null,"Org VDC Edge Gateways").N() + [Array] $Edges = Search-Cloud -QueryType EdgeGateway -Filter "Vdc==$($OrgVdc.Id)" + + foreach ($Edge in $Edges) { + $ps.Add('table','table').Add("tr","bgtitle text-white").Append("th",$null,"Edge Name").N() + $ps.Add("tr").N() + $ps.Append("td",$null,$Edge.Name).Up().N() + + $ps.Add("td","bgsubsection").N() + $ps.Add("table","table bgcolorsub").N() + $ps.Append("tr").Append("td","font-weight-bold","HaStatus").Append("td",$null,($Edge.HaStatus)).N() + $ps.Append("td","font-weight-bold","AdvancedNetworkingEnabled").Append("td",$null,$Edge.AdvancedNetworkingEnabled).N() + $ps.Append("tr").Append("td","font-weight-bold","NumberOfExtNetworks").Append("td",$null,($Edge.NumberOfExtNetworks)).N() + $ps.Append("td","font-weight-bold","NumberOfOrgNetworks").Append("td",$null,$Edge.NumberOfOrgNetworks).N() + + $ps.Up().Up().N() + } + $ps.Up().N() + + ### add Org Networks of this Org VDC to Report + $ps.Main().Append("h3",$null,"Org VDC Networks").N() + [Array] $Networks = $OrgVdc | Get-OrgVdcNetwork + + foreach ($Network in $Networks) { + $ps.Add('table','table').Add("tr","bgtitle text-white").Append("th",$null,"Network Name").N() + $ps.Add("tr").N() + $ps.Append("td",$null,$Network.Name).Up().N() + + $ps.Add("td","bgsubsection").N() + $ps.Add("table","table bgcolorsub").N() + $ps.Append("tr").Append("td","font-weight-bold","DefaultGateway").Append("td",$null,($Network.DefaultGateway)).N() + $ps.Append("td","font-weight-bold","Netmask").Append("td",$null,$Network.Netmask).N() + $ps.Append("tr").Append("td","font-weight-bold","NetworkType").Append("td",$null,($Network.NetworkType)).N() + $ps.Append("td","font-weight-bold","StaticIPPool").Append("td",$null,$Network.StaticIPPool).N() + + $ps.Up().Up().N() + } + $ps.Up().N() + + ### add vApps of this Org VDC to Report + $ps.Main().Append("h3",$null,"Org VDC vApps").N() + + [Array] $Vapps = $OrgVdc | Get-CIVApp + + foreach ($Vapp in $Vapps) { + $ps.Add('table','table').Add("tr","bgtitle text-white").Append("th",$null,"vApp Name").Append("th",$null,"Owner").Up().N() + $ps.Add("tr").N() + $ps.Append("td",$null,$Vapp.Name).Append("td",$null,$Vapp.Owner).Up().N() + + #### add VMs of this vApp to Report + $ps.Add("td","bgsubsection").N() + $ps.Add("table","table bgcolorsub").N() + $ps.Add("tr").N() + + $headers = @("Name","Status","GuestOSFullName","CpuCount","MemoryGB") + foreach ($h in $headers) { + $ps.Append("th",$null,$h).N() + } + $ps.Up().N() + + [Array] $VMs = $Vapp | Get-CIVM + + foreach ($VM in $VMs) { + $ps.Add("tr").N() + $ps.Append("td",$null,$VM.Name).N() + $ps.Append("td",$null,$VM.Status).N() + $ps.Append("td",$null,$VM.GuestOSFullName).N() + $ps.Append("td",$null,$VM.CpuCount).N() + $ps.Append("td",$null,$VM.MemoryGB).N() + + $ps.Up().N() + + } + $ps.Up().Up().N() + + } + $ps.Up().N() + + } + $ps.save($Path) + + "$(Get-Date -Format "yyyy-MM-dd HH:mm:ss") - Open HTML Report..." + Start-Process $Path + +} +} diff --git a/Modules/VMware-vCD-TenantReport/media/Get-VcdTenantReport.png b/Modules/VMware-vCD-TenantReport/media/Get-VcdTenantReport.png new file mode 100644 index 0000000000000000000000000000000000000000..c42dacfdb845605ada5fa72b3bb898333356c5ed GIT binary patch literal 6197 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

" + [xml]$xmlDocument = $null + $onLoadJS = $null + $cssStyles= @{} + $lastEl = $null + $newEl = $null + $indentedOutput = $false + $bootstrapAtCompile = $false + PowerStartHTML([string]$title) { + $this.xmlDocument = $this.PowerStartHtmlTemplate + $this.xmlDocument.html.head.title = $title + $this.lastEl = $this.xmlDocument.html.body.ChildNodes[0] + $this.onLoadJS = New-Object System.Collections.Generic.List[System.String] + } + [string] GetHtml() { + $xmlclone = $this.xmlDocument.Clone() + $csb = [System.Text.StringBuilder]::new() + foreach ($cssStyle in $this.cssStyles.GetEnumerator()) { + $null = $csb.AppendFormat("{0} {{ {1} }}",$cssStyle.Name,$cssStyle.Value) + } + $this.xmlDocument.html.head.style = $csb.toString() + $this.AddBootStrapAtCompile() + if($this.onLoadJS.Count -gt 0) { + $this.onLoadJs.Insert(0,"`r`n`$(document).ready(function() {") + $this.onLoadJs.Add("})`r`n") + $el = $this.xmlDocument.CreateElement("script") + $el.AppendChild($this.xmlDocument.CreateTextNode([System.String]::Join("`r`n",$this.onLoadJs))) + $this.xmlDocument.html.body.AppendChild($el) + } + $ms = [System.IO.MemoryStream]::new() + $xmlWriter = [System.Xml.XmlTextWriter]::new($ms,[System.Text.Encoding]::UTF8) + if($this.indentedOutput) { + $xmlWriter.Formatting = [System.Xml.Formatting]::Indented + } + $this.xmlDocument.WriteContentTo($xmlWriter) + $xmlWriter.Flush() + $ms.Flush() + #make sure that everytime we do gethtml we keep it clean + $this.xmlDocument = $xmlclone + $ms.Position = 0; + $sr = [System.IO.StreamReader]::new($ms); + return ("{0}`r`n" -f $sr.ReadToEnd()) + } + Save($path) { + $this.GetHtml() | Set-Content -path $path -Encoding UTF8 + } + + AddAttr($el,$name,$value) { + $attr = $this.xmlDocument.CreateAttribute($name) + $attr.Value = $value + $el.Attributes.Append($attr) + } + + AddAttrs($el,$dict) { + foreach($a in $dict.GetEnumerator()) { + $this.AddAttr($el,$a.Name,$a.Value) + } + } + [PowerStartHTML] AddBootStrap() { + $this.bootstrapAtCompile = $true + return $this + } + AddJSScript($href,$integrity) { + $el = $this.xmlDocument.CreateElement("script") + $attrs = @{ + "src"="$href"; + "integrity"="$integrity"; + "crossorigin"="anonymous" + } + $this.AddAttrs($el,$attrs) + $el.AppendChild($this.xmlDocument.CreateTextNode("")) + $this.xmlDocument.html.body.AppendChild($el) + } + AddBootStrapAtCompile() { #Bootstrap script needs to be added at the end + if($this.bootstrapAtCompile) { + $el = $this.xmlDocument.CreateElement("link") + $attrs = @{ + "rel"="stylesheet"; + "href"='https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css'; + "integrity"="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M"; + "crossorigin"="anonymous" + } + $this.AddAttrs($el,$attrs) + $el.AppendChild($this.xmlDocument.CreateTextNode("")) + $this.xmlDocument.html.head.AppendChild($el) + $this.AddJSScript('https://code.jquery.com/jquery-3.2.1.slim.min.js',"sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN") + $this.AddJSScript('https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.11.0/umd/popper.min.js',"sha384-b/U6ypiBEHpOf/4+1nzFpr53nxSS+GLCkfwBdFNTxtclqqenISfwAzpKaMNFNmj4") + $this.AddJSScript('https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/js/bootstrap.min.js',"sha384-h0AbiXch4ZDo7tp9hKZ4TsHbi047NrKGLO3SEJAg45jXxnGIfYzk4Si90RDIqNm1") + } + } + [PowerStartHTML] AddContainerAttrToMain() { + $this.AddAttr($this.xmlDocument.html.body.ChildNodes[0],"class","container") + return $this + } + [PowerStartHTML] Append($elType = "table",$className=$null,[string]$text=$null) { + $el = $this.xmlDocument.CreateElement($elType) + if($text -ne $null) { + $el.AppendChild($this.xmlDocument.CreateTextNode($text)) + } + if($className -ne $null) { + $this.AddAttr($el,"class",$className) + } + $this.lastEl.AppendChild($el) + $this.newEl = $el + + return $this + } + [PowerStartHTML] Append($elType = "table",$className=$null) { return $this.Append($elType,$className,$null) } + [PowerStartHTML] Append($elType = "table") { return $this.Append($elType,$null,$null) } + [PowerStartHTML] Add($elType = "table",$className=$null,[string]$text=$null) { + $this.Append($elType,$className,$text) + $this.lastEl = $this.newEl + return $this + } + [PowerStartHTML] Add($elType = "table",$className=$null) { return $this.Add($elType,$className,$null) } + [PowerStartHTML] Add($elType = "table") { return $this.Add($elType,$null,$null) } + [PowerStartHTML] Main() { + $this.lastEl = $this.xmlDocument.html.body.ChildNodes[0]; + return $this + } + [PowerStartHTML] Up() { + $this.lastEl = $this.lastEl.ParentNode; + return $this + } + N() {} +} +class PowerStartHTMLPassThroughLine { + $object;$cells + PowerStartHTMLPassThroughLine($object) { + $this.object = $object; + $this.cells = new-object System.Collections.HashTable; + } +} +class PowerStartHTMLPassThroughElement { + $name;$text;$element;$id + PowerStartHTMLPassThroughElement($name,$text,$element,$id) { + $this.name = $name; $this.text = $text; $this.element = $element;$this.id = $id + } +} +function New-PowerStartHTML { + param( + [Parameter(Mandatory=$true)][string]$title, + [switch]$nobootstrap=$false + ) + $pshtml = (new-object PowerStartHTML($title)) + if(-not $nobootstrap) { + $pshtml.AddBootStrap().AddContainerAttrToMain().N() + } + return $pshtml +} +function Add-PowerStartHTMLTable { + param( + [Parameter(Mandatory=$True,ValueFromPipeline=$True)]$object, + [PowerStartHTML]$psHtml, + [string]$tableTitle = $null, + [string]$tableClass = $null, + [string]$idOverride = $(if($tableTitle -ne $null) {($tableTitle.toLower() -replace "[^a-z0-9]","-") }), + [switch]$passthroughTable = $false, + [switch]$noheaders = $false + ) + begin { + if($tableTitle -ne $null) { + $psHtml.Main().Append("h1",$null,$tableTitle).N() + if($idOverride -ne $null) { + $psHtml.AddAttr($psHtml.newEl,"id","header-$idOverride") + } + } + $psHtml.Main().Add("table").N() + [int]$r = 0 + [int]$c = 0 + if($idOverride -ne $null) { + $psHtml.AddAttr($psHtml.newEl,"id","table-$idOverride") + } + if($tableClass -ne $null) { + $psHtml.AddAttr($psHtml.newEl,"class",$tableClass) + } + [bool]$isFirst = $true + } + process { + $c = 0 + + $props = $object | Get-Member -Type Properties + if(-not $noheaders -and $isFirst) { + $psHtml.Add("tr").N() + if($idOverride -ne $null) { + $psHtml.AddAttr($psHtml.newEl,"id","table-$idOverride-trh") + } + $props | % { + $n = $_.Name; + $psHtml.Append("th",$null,$n).N() + if($idOverride -ne $null) { + $cellid = "table-$idOverride-td-$r-$c" + $psHtml.AddAttr($psHtml.newEl,"id",$cellid) + } + $c++ + } + $c = 0 + $psHtml.Up().N() + } + + $psHtml.Add("tr").N() + if($idOverride -ne $null) { + $psHtml.AddAttr($psHtml.newEl,"id","table-$idOverride-tr-$r") + } + $pstableln = [PowerStartHTMLPassThroughLine]::new($object) + + $props | % { + $n = $_.Name; + $psHtml.Append("td",$null,$object."$n").N() + $cellid = $null + if($idOverride -ne $null) { + $cellid = "table-$idOverride-td-$r-$c" + $psHtml.AddAttr($psHtml.newEl,"id",$cellid) + } + if($passthroughTable) { + $pstableln.cells.Add($n,[PowerStartHTMLPassThroughElement]::new($n,($object."$n"),$psHtml.newEl,$cellid)) + } + + $c++ + } + if($passthroughTable) { + $pstableln + } + $psHtml.Up().N() + $isFirst = $false + $r++ + } + end { + } +} + + +Export-ModuleMember -Function @('New-PowerStartHTML','Add-PowerStartHTMLTable') + + + diff --git a/Modules/VMware-vCD-TenantReport/tests/VMware-vCD-TenantReport.Tests.ps1 b/Modules/VMware-vCD-TenantReport/tests/VMware-vCD-TenantReport.Tests.ps1 new file mode 100644 index 0000000..2effa50 --- /dev/null +++ b/Modules/VMware-vCD-TenantReport/tests/VMware-vCD-TenantReport.Tests.ps1 @@ -0,0 +1,24 @@ +$moduleRoot = Resolve-Path "$PSScriptRoot\.." +$moduleName = "VMware-vCD-TenantReport" + +Describe "General project validation: $moduleName" { + + $scripts = Get-ChildItem $moduleRoot -Include *.ps1, *.psm1, *.psd1 -Recurse + + # TestCases are splatted to the script so we need hashtables + $testCase = $scripts | Foreach-Object {@{file = $_}} + It "Script should be valid powershell" -TestCases $testCase { + param($file) + + $file.fullname | Should Exist + + $contents = Get-Content -Path $file.fullname -ErrorAction Stop + $errors = $null + $null = [System.Management.Automation.PSParser]::Tokenize($contents, [ref]$errors) + $errors.Count | Should Be 0 + } + + It "Module '$moduleName' can import cleanly" { + {Import-Module (Join-Path $moduleRoot "$moduleName.psd1") -force } | Should Not Throw + } +} From b89bd7ab5a013c8ec4df082dc7aa4b1fbf2902c1 Mon Sep 17 00:00:00 2001 From: Sam McGeown Date: Fri, 8 Sep 2017 17:40:08 +0100 Subject: [PATCH 11/23] Added HA vCenter Deploy script Added HA vCenter Deploy script, configuration JSON file and note with link to my blog post detailing how to use it --- Scripts/ha-vcenter-deploy-template.json | 58 ++++ Scripts/ha-vcenter-deploy.md | 4 + Scripts/ha-vcenter-deploy.ps1 | 381 ++++++++++++++++++++++++ 3 files changed, 443 insertions(+) create mode 100644 Scripts/ha-vcenter-deploy-template.json create mode 100644 Scripts/ha-vcenter-deploy.md create mode 100644 Scripts/ha-vcenter-deploy.ps1 diff --git a/Scripts/ha-vcenter-deploy-template.json b/Scripts/ha-vcenter-deploy-template.json new file mode 100644 index 0000000..3ac0079 --- /dev/null +++ b/Scripts/ha-vcenter-deploy-template.json @@ -0,0 +1,58 @@ +{ + "__version": "0.1", + "__comments": "Configuration for ha-vcenter-deploy.ps1 - www.definit.co.uk", + "target": { + "server": "vcsa.definit.local", + "user": "administrator@vsphere.local", + "password": "VMware1!", + "datacenter": "Lab", + "cluster": "Workload", + "datastore": "vsanDatastore", + "folder": "Nested Labs/HA-vCenter", + "portgroup": "HA-vCenter-Management", + "ha-portgroup": "HA-vCenter-Heartbeat", + "network": { + "netmask": "255.255.255.0", + "gateway": "10.0.11.1", + "prefix": "24", + "dns": "192.168.1.20", + "domain": "definit.local", + "ntp": "192.168.1.1" + } + }, + "sources": { + "VCSAInstaller": "e:\\Pod-Deploy\\vSphere\\VMware-VCSA-all-6.5.0-4944578" + }, + "active": { + "deploymentSize": "small", + "name": "ha-vc-active", + "ip": "10.0.11.10", + "ha-ip": "172.16.1.1", + "hostname": "ha-vc.definit.local", + "rootPassword": "VMware1!", + "sso": { + "domain": "vsphere.local", + "site": "Default-First-Site", + "password": "VMware1!" + }, + "datacenter": "HA-vCenter-Datacenter", + "cluster": "HA-vCenter-Cluster-1", + "distributedSwitch": "HA-vCenter-VDS", + "portgroup": "HA-vCenter-PortGroup" + }, + "cluster": { + "passive-ip": "172.16.1.2", + "passive-name": "ha-vc-passive", + "witness-ip": "172.16.1.3", + "witness-name": "ha-vc-witness", + "ha-mask": "255.255.255.248" + }, + "general": { + "syslog": "192.168.1.26", + "ssh": true, + "log": "ha-vcenter-deploy.log" + }, + "license": { + "vcenter": "7H23H-11111-22222-33333-90ZQN" + } +} \ No newline at end of file diff --git a/Scripts/ha-vcenter-deploy.md b/Scripts/ha-vcenter-deploy.md new file mode 100644 index 0000000..91271ca --- /dev/null +++ b/Scripts/ha-vcenter-deploy.md @@ -0,0 +1,4 @@ +# HA-vCenter-Deploy +PowerShell script to deploy a highly available vCenter Server + +See https://www.definit.co.uk/2017/06/powershell-deploying-vcenter-high-availability-in-advanced-mode/ for details diff --git a/Scripts/ha-vcenter-deploy.ps1 b/Scripts/ha-vcenter-deploy.ps1 new file mode 100644 index 0000000..7a9984a --- /dev/null +++ b/Scripts/ha-vcenter-deploy.ps1 @@ -0,0 +1,381 @@ +param( + [Parameter(Mandatory=$true)] [String]$configFile, + [switch]$deployActive, + [switch]$licenseVCSA, + [switch]$addSecondaryNic, + [switch]$prepareVCHA, + [switch]$clonePassiveVM, + [switch]$cloneWitnessVM, + [switch]$configureVCHA, + [switch]$resizeWitness, + [switch]$createDRSRule +) + +if($psboundparameters.count -eq 1) { + # Only the configFile is passed, set all steps to true + $deployActive = $true + $licenseVCSA = $true + $addSecondaryNic = $true + $prepareVCHA = $true + $clonePassiveVM = $true + $cloneWitnessVM = $true + $configureVCHA = $true + $resizeWitness = $true + $createDRSRule = $true +} + +# Import the PowerCLI and DNS modules +Get-Module -ListAvailable VMware*,DnsServer | Import-Module +if ( !(Get-Module -Name VMware.VimAutomation.Core -ErrorAction SilentlyContinue) ) { + throw "PowerCLI must be installed" +} +# Written by Sam McGeown @sammcgeown - www.definit.co.uk +# Hat tips and thanks go to... +# William Lam http://www.virtuallyghetto.com/2016/11/vghetto-automated-vsphere-lab-deployment-for-vsphere-6-0u2-vsphere-6-5.html +# http://www.virtuallyghetto.com/2017/01/exploring-new-vcsa-vami-api-wpowercli-part-1.html + +# Get the folder location +$ScriptLocation = Split-Path -Parent $PSCommandPath + +# Import the JSON Config File +$podConfig = (get-content $($configFile) -Raw) | ConvertFrom-Json + +# Path to VCSA Install Sources +$VCSAInstaller = "$($podConfig.sources.VCSAInstaller)" + +# Log File +$verboseLogFile = $podConfig.general.log + +$StartTime = Get-Date + +Function Write-Log { + param( + [Parameter(Mandatory=$true)] + [String]$message, + [switch]$Warning, + [switch]$Info + ) + $timeStamp = Get-Date -Format "dd-MM-yyyy hh:mm:ss" + Write-Host -NoNewline -ForegroundColor White "[$timestamp]" + if($Warning){ + Write-Host -ForegroundColor Yellow " WARNING: $message" + } elseif($Info) { + Write-Host -ForegroundColor White " $message" + }else { + Write-Host -ForegroundColor Green " $message" + } + $logMessage = "[$timeStamp] $message" | Out-File -Append -LiteralPath $verboseLogFile +} + +function Get-VCSAConnection { + param( + [string]$vcsaName, + [string]$vcsaUser, + [string]$vcsaPassword + ) + $existingConnection = $global:DefaultVIServers | where-object -Property Name -eq -Value $vcsaName + if($existingConnection -ne $null) { + return $existingConnection; + } else { + $connection = Connect-VIServer -Server $vcsaName -User $vcsaUser -Password $vcsaPassword -WarningAction SilentlyContinue; + if($connection -ne $null) { + return $connection; + } else { + throw "Unable to connect to $($vcsaName)..." + } + } +} + +function Close-VCSAConnection { + param( + [string]$vcsaName + ) + if($vcsaName.Length -le 0) { + if($Global:DefaultVIServers -ne $null) { + Disconnect-VIServer -Server $Global:DefaultVIServers -Confirm:$false -ErrorAction SilentlyContinue + } + } else { + $existingConnection = $global:DefaultVIServers | where-object -Property Name -eq -Value $vcsaName + if($existingConnection -ne $null) { + Disconnect-VIServer -Server $existingConnection -Confirm:$false; + } else { + Write-Warning -Message "Could not find an existing connection named $($vcsaName)" + } + } +} + +function Get-PodFolder { + param( + $vcsaConnection, + [string]$folderPath + ) + $folderArray = $folderPath.split("/") + $parentFolder = Get-Folder -Server $vcsaConnection -Name vm + foreach($folder in $folderArray) { + $folderExists = Get-Folder -Server $vcsaConnection | Where-Object -Property Name -eq -Value $folder + if($folderExists -ne $null) { + $parentFolder = $folderExists + } else { + $parentFolder = New-Folder -Name $folder -Location $parentFolder + } + } + return $parentFolder +} + + +Close-VCSAConnection + +if($deployActive) { + Write-Log "#### Deploying Active VCSA ####" + $pVCSA = Get-VCSAConnection -vcsaName $podConfig.target.server -vcsaUser $podConfig.target.user -vcsaPassword $podConfig.target.password + $pCluster = Get-Cluster -Name $podConfig.target.cluster -Server $pVCSA + $pDatastore = Get-Datastore -Name $podConfig.target.datastore -Server $pVCSA + $pPortGroup = Get-VDPortgroup -Name $podConfig.target.portgroup -Server $pVCSA + $pFolder = Get-PodFolder -vcsaConnection $pVCSA -folderPath $podConfig.target.folder + + Write-Log "Disabling DRS on $($podConfig.target.cluster)" + $pCluster | Set-Cluster -DrsEnabled:$true -DrsAutomationLevel:PartiallyAutomated -Confirm:$false | Out-File -Append -LiteralPath $verboseLogFile + + + Write-Log "Creating DNS Record" + Add-DnsServerResourceRecordA -Name $podConfig.active.name -ZoneName $podConfig.target.network.domain -AllowUpdateAny -IPv4Address $podConfig.active.ip -ComputerName "192.168.1.20" -CreatePtr -ErrorAction SilentlyContinue + + Write-Log "Deploying VCSA" + $config = (Get-Content -Raw "$($VCSAInstaller)\vcsa-cli-installer\templates\install\embedded_vCSA_on_VC.json") | convertfrom-json + $config.'new.vcsa'.vc.hostname = $podConfig.target.server + $config.'new.vcsa'.vc.username = $podConfig.target.user + $config.'new.vcsa'.vc.password = $podConfig.target.password + $config.'new.vcsa'.vc.datacenter = @($podConfig.target.datacenter) + $config.'new.vcsa'.vc.datastore = $podConfig.target.datastore + $config.'new.vcsa'.vc.target = @($podConfig.target.cluster) + $config.'new.vcsa'.vc.'deployment.network' = $podConfig.target.portgroup + $config.'new.vcsa'.os.'ssh.enable' = $podConfig.general.ssh + $config.'new.vcsa'.os.password = $podConfig.active.rootPassword + $config.'new.vcsa'.appliance.'thin.disk.mode' = $true + $config.'new.vcsa'.appliance.'deployment.option' = $podConfig.active.deploymentSize + $config.'new.vcsa'.appliance.name = $podConfig.active.name + $config.'new.vcsa'.network.'system.name' = $podConfig.active.hostname + $config.'new.vcsa'.network.'ip.family' = "ipv4" + $config.'new.vcsa'.network.mode = "static" + $config.'new.vcsa'.network.ip = $podConfig.active.ip + $config.'new.vcsa'.network.'dns.servers'[0] = $podConfig.target.network.dns + $config.'new.vcsa'.network.prefix = $podConfig.target.network.prefix + $config.'new.vcsa'.network.gateway = $podConfig.target.network.gateway + $config.'new.vcsa'.sso.password = $podConfig.active.sso.password + $config.'new.vcsa'.sso.'domain-name' = $podConfig.active.sso.domain + $config.'new.vcsa'.sso.'site-name' = $podConfig.active.sso.site + + Write-Log "Creating VCSA JSON Configuration file for deployment" + + $config | ConvertTo-Json | Set-Content -Path "$($ENV:Temp)\active.json" + if((Get-VM | Where-Object -Property Name -eq -Value $podConfig.active.name) -eq $null) { + Write-Log "Deploying OVF, this may take a while..." + Invoke-Expression "$($VCSAInstaller)\vcsa-cli-installer\win32\vcsa-deploy.exe install --no-esx-ssl-verify --accept-eula --acknowledge-ceip $($ENV:Temp)\active.json"| Out-File -Append -LiteralPath $verboseLogFile + $vcsaDeployOutput | Out-File -Append -LiteralPath $verboseLogFile + Write-Log "Moving $($podConfig.active.name) to $($podConfig.target.folder)" + if((Get-VM | where {$_.name -eq $podConfig.active.name}) -eq $null) { + throw "Could not find VCSA VM. The script was unable to find the deployed VCSA" + } + Get-VM -Name $podConfig.active.name | Move-VM -Destination $pFolder | Out-File -Append -LiteralPath $verboseLogFile + } else { + Write-Log "VCSA exists, skipping" -Warning + } + Close-VCSAConnection +} + + +if($licenseVCSA) { + Write-Log "#### Configuring VCSA ####" + Write-Log "Getting connection to the new VCSA" + $nVCSA = Get-VCSAConnection -vcsaName $podConfig.active.ip -vcsaUser "administrator@$($podConfig.active.sso.domain)" -vcsaPassword $podConfig.active.sso.password + + Write-Log "Installing vCenter License" + $serviceInstance = Get-View ServiceInstance -Server $nVCSA + $licenseManagerRef=$serviceInstance.Content.LicenseManager + $licenseManager=Get-View $licenseManagerRef + $licenseManager.AddLicense($podConfig.license.vcenter,$null) | Out-File -Append -LiteralPath $verboseLogFile + $licenseAssignmentManager = Get-View $licenseManager.LicenseAssignmentManager + Write-Log "Assigning vCenter Server License" + try { + $licenseAssignmentManager.UpdateAssignedLicense($nVCSA.InstanceUuid, $podConfig.license.vcenter, $null) | Out-File -Append -LiteralPath $verboseLogFile + } + catch { + $ErrorMessage = $_.Exception.Message + Write-Log $ErrorMessage -Warning + } + Close-VCSAConnection -vcsaName $podConfig.active.ip +} + +if($addSecondaryNic) { + Write-Log "#### Adding HA Network Adapter ####" + $pVCSA = Get-VCSAConnection -vcsaName $podConfig.target.server -vcsaUser $podConfig.target.user -vcsaPassword $podConfig.target.password + + if((Get-VM -Server $pVCSA -Name $podConfig.active.name | Get-NetworkAdapter).count -le 1) { + Write-Log "Adding HA interface" + Get-VM -Server $pVCSA -Name $podConfig.active.name | New-NetworkAdapter -Portgroup (Get-VDPortgroup -Name $podConfig.target."ha-portgroup") -Type Vmxnet3 -StartConnected | Out-File -Append -LiteralPath $verboseLogFile + } + Close-VCSAConnection + + $nVCSA = Get-VCSAConnection -vcsaName $podConfig.active.ip -vcsaUser "administrator@$($podConfig.active.sso.domain)" -vcsaPassword $podConfig.active.sso.password + + Write-Log "Configuring HA interface" + $CisServer = Connect-CisServer -Server $podConfig.active.ip -User "administrator@$($podConfig.active.sso.domain)" -Password $podConfig.active.sso.password + + $ipv4API = (Get-CisService -Name 'com.vmware.appliance.techpreview.networking.ipv4') + $specList = $ipv4API.Help.set.config.CreateExample() + $createSpec = [pscustomobject] @{ + address = $podConfig.active."ha-ip"; + default_gateway = ""; + interface_name = "nic1"; + mode = "is_static"; + prefix = "29"; + } + $specList += $createSpec + $ipv4API.set($specList) + Close-VCSAConnection +} + +if($prepareVCHA) { + Write-Log "#### Preparing vCenter HA mode ####" + + $nVCSA = Get-VCSAConnection -vcsaName $podConfig.active.ip -vcsaUser "administrator@$($podConfig.active.sso.domain)" -vcsaPassword $podConfig.active.sso.password + + Write-Log "Preparing vCenter HA" + $ClusterConfig = Get-View failoverClusterConfigurator + + $PassiveIpSpec = New-Object VMware.Vim.CustomizationFixedIp + $PassiveIpSpec.IpAddress = $podConfig.cluster."passive-ip" + + $PassiveNetwork = New-object VMware.Vim.CustomizationIPSettings + $PassiveNetwork.Ip = $PassiveIpSpec + $PassiveNetwork.SubnetMask = $podConfig.cluster."ha-mask" + + $PassiveNetworkSpec = New-Object Vmware.Vim.PassiveNodeNetworkSpec + $PassiveNetworkSpec.IpSettings = $PassiveNetwork + + $WitnessIpSpec = New-Object VMware.Vim.CustomizationFixedIp + $WitnessIpSpec.IpAddress = $podConfig.cluster."witness-ip" + + $WitnessNetwork = New-object VMware.Vim.CustomizationIPSettings + $WitnessNetwork.Ip = $WitnessIpSpec + $WitnessNetwork.SubnetMask = $podConfig.cluster."ha-mask" + + $WitnessNetworkSpec = New-Object VMware.Vim.NodeNetworkSpec + $WitnessNetworkSpec.IpSettings = $WitnessNetwork + + $ClusterNetworkSpec = New-Object VMware.Vim.VchaClusterNetworkSpec + $ClusterNetworkSpec.WitnessNetworkSpec = $WitnessNetworkSpec + $ClusterNetworkSpec.PassiveNetworkSpec = $PassiveNetworkSpec + + $PrepareTask = $ClusterConfig.prepareVcha_task($ClusterNetworkSpec) + + Close-VCSAConnection +} + +if($clonePassiveVM) { + Write-Log "#### Cloning VCSA for Passive Node ####" + + $pVCSA = Get-VCSAConnection -vcsaName $podConfig.target.server -vcsaUser $podConfig.target.user -vcsaPassword $podConfig.target.password + $pVMHost = Get-Random (Get-VMhost -Location $podConfig.target.cluster) + $pFolder = Get-PodFolder -vcsaConnection $pVCSA -folderPath $podConfig.target.folder + + $activeVM = Get-VM -Name $podConfig.active.name + $CloneSpecName = "vCHA_ClonePassive" + + Write-Log "Creating customization spec" + # Clean up any old spec + Get-OSCustomizationSpec -Name $CloneSpecName -ErrorAction SilentlyContinue | Remove-OSCustomizationSpec -Confirm:$false -ErrorAction SilentlyContinue | Out-File -Append -LiteralPath $verboseLogFile + New-OSCustomizationSpec -Name $CloneSpecName -OSType Linux -Domain $podConfig.target.network.domain -NamingScheme fixed -DnsSuffix $podConfig.target.network.domain -NamingPrefix $podConfig.active.hostname -DnsServer $podConfig.target.network.dns -Type NonPersistent | Out-File -Append -LiteralPath $verboseLogFile + Get-OSCustomizationNicMapping -OSCustomizationSpec $CloneSpecName | Set-OSCustomizationNicMapping -IpMode UseStaticIP -IpAddress $podConfig.active.ip -SubnetMask $podConfig.target.network.netmask -DefaultGateway $podConfig.target.network.gateway | Out-File -Append -LiteralPath $verboseLogFile + New-OSCustomizationNicMapping -OSCustomizationSpec $CloneSpecName -IpMode UseStaticIP -IpAddress $podConfig.cluster."passive-ip" -SubnetMask $podConfig.cluster."ha-mask" -DefaultGateway $podConfig.target.network.gateway | Out-File -Append -LiteralPath $verboseLogFile + + Write-Log "Cloning Active VCSA to Passive VCSA" + $passiveVM = New-VM -Name $podConfig.cluster."passive-name" -VM $activeVM -OSCustomizationSpec $CloneSpecName -VMhost $pVMHost -Server $pVCSA -Location $pFolder | Start-VM | Out-File -Append -LiteralPath $verboseLogFile + + # Ensure the network adapters are connected + $passiveVM | Get-NetworkAdapter | Set-NetworkAdapter -Connected:$true -Confirm:$false + + Write-Log "Waiting for VMware Tools" + $passiveVM | Wait-Tools + + Close-VCSAConnection +} + +if($cloneWitnessVM) { + Write-Log "#### Cloning VCSA for Witness Node ####" + + $pVCSA = Get-VCSAConnection -vcsaName $podConfig.target.server -vcsaUser $podConfig.target.user -vcsaPassword $podConfig.target.password + $pVMHost = Get-Random (Get-VMhost -Location $podConfig.target.cluster) + $pFolder = Get-PodFolder -vcsaConnection $pVCSA -folderPath $podConfig.target.folder + + $activeVM = Get-VM -Name $podConfig.active.name + $CloneSpecName = "vCHA_CloneWitness" + + Write-Log "Creating customization spec" + # Clean up any old spec + Get-OSCustomizationSpec -Name $CloneSpecName -ErrorAction SilentlyContinue | Remove-OSCustomizationSpec -Confirm:$false -ErrorAction SilentlyContinue | Out-File -Append -LiteralPath $verboseLogFile + New-OSCustomizationSpec -Name $CloneSpecName -OSType Linux -Domain $podConfig.target.network.domain -NamingScheme fixed -DnsSuffix $podConfig.target.network.domain -NamingPrefix $podConfig.active.hostname -DnsServer $podConfig.target.network.dns -Type NonPersistent | Out-File -Append -LiteralPath $verboseLogFile + New-OSCustomizationNicMapping -OSCustomizationSpec $CloneSpecName -IpMode UseStaticIP -IpAddress $podConfig.cluster."witness-ip" -SubnetMask $podConfig.cluster."ha-mask" -DefaultGateway $podConfig.target.network.gateway | Out-File -Append -LiteralPath $verboseLogFile + + Write-Log "Cloning Active VCSA to Witness VCSA" + $witnessVM = New-VM -Name $podConfig.cluster."witness-name" -VM $activeVM -OSCustomizationSpec $CloneSpecName -VMhost $pVMHost -Server $pVCSA -Location $pFolder | Start-VM | Out-File -Append -LiteralPath $verboseLogFile + + # Ensure the network adapters are connected + $witnessVM | Get-NetworkAdapter | Set-NetworkAdapter -Connected:$true -Confirm:$false + + Write-Log "Waiting for VMware Tools" + $witnessVM | Wait-Tools + + Close-VCSAConnection +} + +if($configureVCHA) { + Write-Log "#### Configuring vCenter HA mode ####" + + $nVCSA = Get-VCSAConnection -vcsaName $podConfig.active.ip -vcsaUser "administrator@$($podConfig.active.sso.domain)" -vcsaPassword $podConfig.active.sso.password + + $ClusterConfig = Get-View failoverClusterConfigurator + $ClusterConfigSpec = New-Object VMware.Vim.VchaClusterConfigSpec + $ClusterConfigSpec.PassiveIp = $podConfig.cluster."passive-ip" + $ClusterConfigSpec.WitnessIp = $podConfig.cluster."witness-ip" + $ConfigureTask = $ClusterConfig.configureVcha_task($ClusterConfigSpec) + Write-Log "Waiting for cluster configuration task" + Start-Sleep -Seconds 30 + + Close-VCSAConnection -vcsaName $podConfig.active.ip +} + +if($resizeWitness) { + Write-Log "#### Resizing Witness Node ####" + $pVCSA = Get-VCSAConnection -vcsaName $podConfig.target.server -vcsaUser $podConfig.target.user -vcsaPassword $podConfig.target.password + + $witnessVM = Get-VM -Name $podConfig.cluster."witness-name" + Write-Log "Waiting for Witness node to shut down" + $witnessVM | Stop-VMGuest -Confirm:$false | Out-File -Append -LiteralPath $verboseLogFile + do { + Start-Sleep -Seconds 3 + $witnessVM = Get-VM -Name $podConfig.cluster."witness-name" + } until($witnessVM.PowerState -eq "Poweredoff") + Write-Log "Setting CPU and Memory" + $witnessVM | Set-VM -MemoryGB 1 -NumCpu 1 -Confirm:$false | Out-File -Append -LiteralPath $verboseLogFile + Write-Log "Starting Witness VM" + $witnessVM | Start-VM | Out-File -Append -LiteralPath $verboseLogFile + Close-VCSAConnection +} + +if($createDRSRule) { + Write-Log "#### Creating DRS Rule ####" + $pVCSA = Get-VCSAConnection -vcsaName $podConfig.target.server -vcsaUser $podConfig.target.user -vcsaPassword $podConfig.target.password + $pCluster = Get-Cluster $podConfig.target.cluster + $vCHA = Get-VM -Name $podConfig.active.name,$podConfig.cluster."passive-name",$podConfig.cluster."witness-name" + New-DRSRule -Name "vCenter HA" -Cluster $pCluster -VM $vCHA -KeepTogether $false | Out-File -Append -LiteralPath $verboseLogFile + Write-Log "Enabling DRS on $($podConfig.target.cluster)" + $pCluster | Set-Cluster -DrsEnabled:$true -DrsAutomationLevel:FullyAutomated -Confirm:$false | Out-File -Append -LiteralPath $verboseLogFile + Close-VCSAConnection +} + + +$EndTime = Get-Date +$duration = [math]::Round((New-TimeSpan -Start $StartTime -End $EndTime).TotalMinutes,2) + +Write-Log "Pod Deployment Completed in $($duration) minutes" \ No newline at end of file From b8055b7ed80061617e004852ab0aa29a562f7279 Mon Sep 17 00:00:00 2001 From: Sam McGeown Date: Fri, 8 Sep 2017 17:44:16 +0100 Subject: [PATCH 12/23] Added script description removed extra .md file --- Scripts/ha-vcenter-deploy.md | 4 ---- Scripts/ha-vcenter-deploy.ps1 | 7 +++++++ 2 files changed, 7 insertions(+), 4 deletions(-) delete mode 100644 Scripts/ha-vcenter-deploy.md diff --git a/Scripts/ha-vcenter-deploy.md b/Scripts/ha-vcenter-deploy.md deleted file mode 100644 index 91271ca..0000000 --- a/Scripts/ha-vcenter-deploy.md +++ /dev/null @@ -1,4 +0,0 @@ -# HA-vCenter-Deploy -PowerShell script to deploy a highly available vCenter Server - -See https://www.definit.co.uk/2017/06/powershell-deploying-vcenter-high-availability-in-advanced-mode/ for details diff --git a/Scripts/ha-vcenter-deploy.ps1 b/Scripts/ha-vcenter-deploy.ps1 index 7a9984a..af215dc 100644 --- a/Scripts/ha-vcenter-deploy.ps1 +++ b/Scripts/ha-vcenter-deploy.ps1 @@ -1,3 +1,10 @@ +<# +Script name: ha-vcenter-deploy.ps1 +Created on: 30/06/2017 +Author: Sam McGeown, @sammcgeown +Description: The purpose of the script is to deploy vCenter in High Availability mode, using the advanced method. See https://www.definit.co.uk/2017/06/powershell-deploying-vcenter-high-availability-in-advanced-mode/ +Dependencies: None known +#> param( [Parameter(Mandatory=$true)] [String]$configFile, [switch]$deployActive, From 2b9aa80b36a54befe058cf911a15d5dca2062f41 Mon Sep 17 00:00:00 2001 From: soggychipsnz <31921723+soggychipsnz@users.noreply.github.com> Date: Wed, 13 Sep 2017 11:23:36 +0100 Subject: [PATCH 13/23] get-peakvms This script will interrogate vcenter to find the peak users of network or storage usage. This was quite handy for me to quickly identify the source of issues such as an inbound DDOS, outbound DOS or a server pummiling storage due to swapping/etc. --- get-peakvms.ps1 | 181 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 181 insertions(+) create mode 100644 get-peakvms.ps1 diff --git a/get-peakvms.ps1 b/get-peakvms.ps1 new file mode 100644 index 0000000..79d7eb3 --- /dev/null +++ b/get-peakvms.ps1 @@ -0,0 +1,181 @@ +<# +Script name: get-peakvms.ps1 +Created on: 09/06/2016 +Author: Scott White, @soggychipsnz, https://github.com/LatexGolem +Description: This script will interrogate vcenter to find the peak users of network or storage usage. +This was quite handy for me to quickly identify the source of issues such as an inbound DDOS, outbound DOS or a server pummiling storage due to swapping/etc. +I would suggest that when you first run the script you run it in host mode, which will identify where the hotspot is. Then once the hot host has been identified, run it in VM mode. +For each statistic the number of operations/packets is collectioned along ith the throughput split into reads or writes/sent or recieved - giving you peak and 95th percentiles +Really need to get around to tidying this up :) +Results will be outputted to OGV and into a timestamped CSV in the current working directory +Dependencies: Nothing special. Assumes vsphere logging has 20 second samples for the last 60 minutes and 5 minute samples in the past 24 hours. +#> + +$clusterfilter = "*" + +#Gather basic stuff +do{ + $modes = @("host","vm") + $mode = $modes | OGV -PassThru -Title "Select mode of operation" + $stats = @("Disk Throughput","Network Throughput") + $stat = $stats | OGV -PassThru -Title "Select type of Stat to check" + if (($mode.count -eq 2) -or ($stat.count -eq 2) ){write-host "Please select only a single mode of operation and statistic type."} +}while ($mode.count -eq 2 -and $stat.count -eq 2) + +$hour = 1..24 +$hour = $hour | OGV -PassThru -Title "Select number of hours to go back in time" + + +#Helper Stuff +$start = (Get-Date).addHours(-$hour) +$finish = (Get-Date) +$duration = $finish - $start | %{[int]$_.TotalSeconds} +#If Start is within the last hour, use 20 second sampling otherwise 5 minute avg +if (((Get-Date) - $start ).TotalSeconds -gt 3700) {$interval=300} else {$interval=20} + +function getstats($starttime,$endtime,$sample,$stat,$entity){ + (Get-Stat -Entity $entity -Stat $stat -IntervalSecs $sample -Start $starttime -Finish $endtime).value | measure -Sum | %{$_.sum} +} + + +if ($mode -eq "host"){ + $clusters = get-cluster $clusterfilter + $vmhosts = $clusters | OGV -PassThru -Title "Select Cluster(s) to target, to get all member Hosts"| get-vmhost + + if ($stat -eq "Network Throughput"){ + $master = @() + $vmhosts | %{ + $metric ="net.packetsRx.summation" + $pktrx = getstats $start $finish $interval $metric $_ + + $metric ="net.packetsTx.summation" + $pkttx = getstats $start $finish $interval $metric $_ + + $metric ="net.bytesRx.average" + $bytesrx = getstats $start $finish $interval $metric $_ + + $metric ="net.bytesTx.average" + $bytestx = getstats $start $finish $interval $metric $_ + + $row = "" | select name,pktrx,pkttx,bytesrx,bytestx + $row.name = $_.name + $row.pktrx = $pktrx + $row.pkttx = $pkttx + $row.bytesrx = $bytesrx + $row.bytestx = $bytestx + $master += $row + } + $master | ogv #sort -Property name | Format-Table + } + if ($stat -eq "Disk Throughput"){ + #Target the datastore, just one. + $datastore = Get-Datastore -vmhost $vmhost[0]| OGV -PassThru -Title "Select target datastore" + #Yes this is fugly. + $instance = ($datastore | Get-View).Info.Url.Split("/") | select -last 2 | select -First 1 + + $master = @() + $vmhosts | %{ + $metric ="datastore.datastoreReadIops.latest" + $rop = (Get-Stat -Entity $_ -Stat $metric -IntervalSecs $interval -Start $start -Finish $finish | ?{$_.Instance -match $instance}).value | measure -Sum | %{$_.sum} + + $metric ="datastore.datastoreWriteIops.latest" + $wrop = (Get-Stat -Entity $_ -Stat $metric -IntervalSecs $interval -Start $start -Finish $finish | ?{$_.Instance -match $instance}).value | measure -Sum | %{$_.sum} + + $metric ="datastore.datastoreReadBytes.latest" + $rbytes = (Get-Stat -Entity $_ -Stat $metric -IntervalSecs $interval -Start $start -Finish $finish | ?{$_.Instance -match $instance}).value | measure -Sum | %{$_.sum} + + $metric ="datastore.datastoreWriteBytes.latest" + $wrbytes = (Get-Stat -Entity $_ -Stat $metric -IntervalSecs $interval -Start $start -Finish $finish | ?{$_.Instance -match $instance}).value | measure -Sum | %{$_.sum} + + $row = "" | select name,rop,wrop,rbytes,wrbytes + $row.name = $_.name + $row.rop = $rop + $row.wrop = $wrop + $row.rbytes = $rbytes + $row.wrbytes = $wrbytes + $master += $row + } + $master | ogv + } + +}if ($mode -eq "vm"){ + Write-Host "Do note doing things on a vmbasis take quite some time (please isolate on a host basis first)" + #Currently only works on a cluster basis + $clusters = get-cluster $clusterfilter + $vms = $clusters |get-vmhost | OGV -PassThru -Title "Select Hosts(s) to target, to get all member VMs"| get-vm | ?{$_.powerstate -match "poweredOn"} + + if ($stat -eq "Network Throughput"){ + $master = new-object system.collections.arraylist + $vms | %{ + $metric ="net.packetsRx.summation" + $pktrx = getstats $start $finish $interval $metric $_ + + $metric ="net.packetsTx.summation" + $pkttx = getstats $start $finish $interval $metric $_ + + $metric ="net.bytesRx.average" + $bytesrx = getstats $start $finish $interval $metric $_ + + $metric ="net.bytesTx.average" + $bytestx = getstats $start $finish $interval $metric $_ + + $row = "" | select name,pktrx,pkttx,bytesrx,bytestx + $row.name = $_.name + $row.pktrx = $pktrx + $row.pkttx = $pkttx + $row.bytesrx = $bytesrx + $row.bytestx = $bytestx + $master += $row + } + $master | ogv + } + if ($stat -eq "Disk Throughput"){ + $master = new-object system.collections.arraylist + + $vms = $vms |?{$_.PowerState -eq "PoweredOn"} + foreach ($vm in $vms){ + $row = ""| select name,iops,riops,riops95,rpeak,wiops,wiops95,wpeak,throughputGBpduration,wMBps,rMBps,datastore,used,prov,iops95 + + $metric = "datastore.numberReadAveraged.average" + $rawdata = Get-Stat -Entity $vm -Stat $metric -IntervalSecs $interval -Start $start -Finish $finish + $tmp = $rawdata.value | measure -average -Maximum + $row.riops = [int] $tmp.average + $row.rpeak = [int] $tmp.Maximum + $row.riops95 = ($rawdata.value | sort)[[math]::Round(($rawdata.count-1) * .95)] + + $metric = "datastore.numberwriteaveraged.average" + $rawdata = Get-Stat -Entity $vm -Stat $metric -IntervalSecs $interval -Start $start -Finish $finish + $tmp = $rawdata.value | measure -average -Maximum + $row.wiops = [int] $tmp.average + $row.wpeak = [int] $tmp.Maximum + $row.wiops95 = ($rawdata.value | sort)[[math]::Round(($rawdata.count-1) * .95)] + + $row.iops = ($row.wiops + $row.riops) + + $metric = "datastore.write.average" + $rawdatawr = Get-Stat -Entity $vm -Stat $metric -IntervalSecs $interval -Start $start -Finish $finish + + $metric = "datastore.read.average" + $rawdatar = Get-Stat -Entity $vm -Stat $metric -IntervalSecs $interval -Start $start -Finish $finish + $reads = $rawdatar.value | measure -Sum | %{$_.sum}| %{$_ /($duration/$interval)/1024/1024} + $writes = $rawdatawr.value | measure -Sum | %{$_.sum}| %{$_ /($duration/$interval)/1024/1024} + $total = $reads * $duration + $writes * $duration + + + $row.name = $vm.name + $row.throughputGBpduration = [decimal]::round($total,2) + $row.wMBps = [decimal]::round($writes*1024,2) + $row.rMBps = [decimal]::round($reads*1024,2) + $row.datastore = ($vm.DatastoreIdList[0] | Get-VIObjectByVIView).name #(($vm.DatastoreIdList| select -First 1 | Get-VIObjectByVIView).Name) + + $row.used = [System.Math]::Round(($vm.UsedSpaceGB)) + $row.prov = [System.Math]::Round(($vm.ProvisionedSpaceGB)) + $row.iops95 = $row.riops95 + $row.wiops95 + + $master += $row + } + $master | ogv + } + +} +$master | Export-Csv "$mode$stat-$(get-date -Format HHmm).csv" From 0d461ab72b06a3e6da4d9d6d547dfc9143458590 Mon Sep 17 00:00:00 2001 From: Alan Comstock <31929022+Mr-Uptime@users.noreply.github.com> Date: Wed, 13 Sep 2017 10:29:57 -0500 Subject: [PATCH 14/23] Create SetMultiPathToRoundRobin --- Scripts/SetMultiPathToRoundRobin | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 Scripts/SetMultiPathToRoundRobin diff --git a/Scripts/SetMultiPathToRoundRobin b/Scripts/SetMultiPathToRoundRobin new file mode 100644 index 0000000..382cbd3 --- /dev/null +++ b/Scripts/SetMultiPathToRoundRobin @@ -0,0 +1,6 @@ +#Check a host for any Fibre Channel devices that are not set to Round Robin. Modify to check clusters if needed. +Get-VMhost VMHOSTNAME | Get-VMHostHba -Type "FibreChannel" | Get-ScsiLun -LunType disk | Where { $_.MultipathPolicy -notlike "RoundRobin" } | Select CanonicalName,MultipathPolicy + +#Set the Multipathing Policy on a host to Round Robin for any Fibre Channel devices that are not Round Robin +$scsilun = Get-VMhost VMHOSTNAME | Get-VMHostHba -Type "FibreChannel" | Get-ScsiLun -LunType disk | Where { $_.MultipathPolicy -notlike "RoundRobin" } +Set-ScsiLun -ScsiLun $scsilun -MultipathPolicy RoundRobin From b10e5606150bfc47ce3dca8d947c4ed124306cce Mon Sep 17 00:00:00 2001 From: Alan Comstock <31929022+Mr-Uptime@users.noreply.github.com> Date: Wed, 13 Sep 2017 12:37:15 -0500 Subject: [PATCH 15/23] Update SetMultiPathToRoundRobin --- Scripts/SetMultiPathToRoundRobin | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/Scripts/SetMultiPathToRoundRobin b/Scripts/SetMultiPathToRoundRobin index 382cbd3..c1a6253 100644 --- a/Scripts/SetMultiPathToRoundRobin +++ b/Scripts/SetMultiPathToRoundRobin @@ -1,6 +1,17 @@ +<# +Script name: SetMultiPathToRoundRobin.ps1 +Created on: 09/13/2017 +Author: Alan Comstock, @Mr_Uptime +Description: Set the MultiPath policy for FC devices to RoundRobin +Dependencies: None known +PowerCLI Version: VMware PowerCLI 6.5 Release 1 build 4624819 +PowerShell Version: 5.1.14393.1532 +OS Version: Windows 10 +#> + #Check a host for any Fibre Channel devices that are not set to Round Robin. Modify to check clusters if needed. -Get-VMhost VMHOSTNAME | Get-VMHostHba -Type "FibreChannel" | Get-ScsiLun -LunType disk | Where { $_.MultipathPolicy -notlike "RoundRobin" } | Select CanonicalName,MultipathPolicy +Get-VMhost HOSTNAME | Get-VMHostHba -Type "FibreChannel" | Get-ScsiLun -LunType disk | Where { $_.MultipathPolicy -notlike "RoundRobin" } | Select CanonicalName,MultipathPolicy #Set the Multipathing Policy on a host to Round Robin for any Fibre Channel devices that are not Round Robin -$scsilun = Get-VMhost VMHOSTNAME | Get-VMHostHba -Type "FibreChannel" | Get-ScsiLun -LunType disk | Where { $_.MultipathPolicy -notlike "RoundRobin" } +$scsilun = Get-VMhost HOSTNAME | Get-VMHostHba -Type "FibreChannel" | Get-ScsiLun -LunType disk | Where { $_.MultipathPolicy -notlike "RoundRobin" } Set-ScsiLun -ScsiLun $scsilun -MultipathPolicy RoundRobin From fee53a0565d319b82817880871056716d0d343da Mon Sep 17 00:00:00 2001 From: Alan Comstock <31929022+Mr-Uptime@users.noreply.github.com> Date: Thu, 14 Sep 2017 10:42:04 -0500 Subject: [PATCH 16/23] Create SetClusterMultiPathToRoundRobin.ps1 --- Scripts/SetClusterMultiPathToRoundRobin.ps1 | 29 +++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 Scripts/SetClusterMultiPathToRoundRobin.ps1 diff --git a/Scripts/SetClusterMultiPathToRoundRobin.ps1 b/Scripts/SetClusterMultiPathToRoundRobin.ps1 new file mode 100644 index 0000000..97bf311 --- /dev/null +++ b/Scripts/SetClusterMultiPathToRoundRobin.ps1 @@ -0,0 +1,29 @@ +<# + Script name: SetClusterMultiPathToRoundRobin.ps1 + Created on: 09/14/2017 + Author: Alan Comstock, @Mr_Uptime + Description: Set the MultiPath policy for FC devices to RoundRobin for all hosts in a cluster. + Dependencies: None known + PowerCLI Version: VMware PowerCLI 6.5 Release 1 build 4624819 + PowerShell Version: 5.1.14393.1532 + OS Version: Windows 10 +#> + +#Check for any Fibre Channel devices that are not set to Round Robin in a cluster. +#Get-Cluster -Name CLUSTERNAME | Get-VMhost | Get-VMHostHba -Type "FibreChannel" | Get-ScsiLun -LunType disk | Where { $_.MultipathPolicy -notlike "RoundRobin" } | Select CanonicalName,MultipathPolicy + +#Set the Multipathing Policy to Round Robin for any Fibre Channel devices that are not Round Robin in a cluster +$cluster = Get-Cluster CLUSTERNAME +$hostlist = Get-VMHost -Location $cluster | Sort Name +$TotalHostCount = $hostlist.count +$hostincrement = 0 +while ($hostincrement -lt $TotalHostCount){ #Host Loop + $currenthost = $hostlist[$hostincrement].Name + Write-Host "Working on" $currenthost + $scsilun = Get-VMhost $currenthost | Get-VMHostHba -Type "FibreChannel" | Get-ScsiLun -LunType disk | Where { $_.MultipathPolicy -notlike "RoundRobin" } + if ($scsilun -ne $null){ + Set-ScsiLun -ScsiLun $scsilun -MultipathPolicy RoundRobin + } + $hostincrement++ #bump the host increment +} +#The End From 434d8c2b9b32bc17fde46024528fdc849444a5fe Mon Sep 17 00:00:00 2001 From: Chris Date: Wed, 20 Sep 2017 19:34:38 +0100 Subject: [PATCH 17/23] Created Get-TotalDiskUsage --- Scripts/Get-TotalDiskUsage | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 Scripts/Get-TotalDiskUsage diff --git a/Scripts/Get-TotalDiskUsage b/Scripts/Get-TotalDiskUsage new file mode 100644 index 0000000..b14e7d5 --- /dev/null +++ b/Scripts/Get-TotalDiskUsage @@ -0,0 +1,4 @@ +#Script returns total disk usage by all Powered On VMs in the environment in Gigabytes +#Author: Chris Bradshaw via https://isjw.uk/using-powercli-to-measure-vm-disk-space-usage/ + +[math]::Round(((get-vm | Where-object{$_.PowerState -eq "PoweredOn" }).UsedSpaceGB | measure-Object -Sum).Sum) From da2cf62c1b835f9e46b7f10e4e13c091f6cb303e Mon Sep 17 00:00:00 2001 From: Chris Date: Wed, 20 Sep 2017 19:37:36 +0100 Subject: [PATCH 18/23] Create Get-TotalMemoryAllocation.ps1 --- Scripts/Get-TotalMemoryAllocation.ps1 | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 Scripts/Get-TotalMemoryAllocation.ps1 diff --git a/Scripts/Get-TotalMemoryAllocation.ps1 b/Scripts/Get-TotalMemoryAllocation.ps1 new file mode 100644 index 0000000..6e6736d --- /dev/null +++ b/Scripts/Get-TotalMemoryAllocation.ps1 @@ -0,0 +1,6 @@ +#Script gets total memory allocation in GB of all powered on VMs in the environment +#Author: Chris Bradshaw via https://isjw.uk/powercli-snippet-total-memory-allocation/ + +[System.Math]::Round(((get-vm | + where-object{$_.PowerState -eq "PoweredOn" }).MemoryGB | + Measure-Object -Sum).Sum ,0) From 78d606ab44228ce6404a4b4ad247cd102fa5ff11 Mon Sep 17 00:00:00 2001 From: Chris Date: Wed, 20 Sep 2017 19:38:14 +0100 Subject: [PATCH 19/23] Rename Get-TotalDiskUsage to Get-TotalDiskUsage.ps1 --- Scripts/{Get-TotalDiskUsage => Get-TotalDiskUsage.ps1} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Scripts/{Get-TotalDiskUsage => Get-TotalDiskUsage.ps1} (100%) diff --git a/Scripts/Get-TotalDiskUsage b/Scripts/Get-TotalDiskUsage.ps1 similarity index 100% rename from Scripts/Get-TotalDiskUsage rename to Scripts/Get-TotalDiskUsage.ps1 From 3b3197b04acfa3eaa9e25cace1cfe686db06640a Mon Sep 17 00:00:00 2001 From: Lukas Winn <30748942+lukaswinn@users.noreply.github.com> Date: Thu, 21 Sep 2017 16:46:41 +0100 Subject: [PATCH 20/23] Create vCenterSnapshot.ps1 Script to retrieve snapshot information for all VM's in a given vCenter --- Scripts/vCenterSnapshot.ps1 | 40 +++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 Scripts/vCenterSnapshot.ps1 diff --git a/Scripts/vCenterSnapshot.ps1 b/Scripts/vCenterSnapshot.ps1 new file mode 100644 index 0000000..415aca8 --- /dev/null +++ b/Scripts/vCenterSnapshot.ps1 @@ -0,0 +1,40 @@ +<# + .NOTES + Script name: vCenterSnapshot.ps1 + Created on: 20/09/2017 + Author: Lukas Winn, @lukaswinn + Dependencies: Password is set to VMware123 in my test environment but this can be changed. + + .DESCRIPTION + Script to retrieve snapshot information for all VM's in a given vCenter + +#> +Write-Host "`nGet VM Snapshot Information!" +Write-Host "Copyright 2017 Lukas Winn / @lukaswinn" +Write-Host "Version 1.0" "`n" + +$vCenter = Read-Host -prompt 'Enter FQDN / IP address of vCenter' + +if ($vCenter) { + $vcUser = Read-Host -prompt 'Username' + +Write-Host 'vCenter:' $vCenter '' + +# Connect to vCenter with $vCenter variable value +Connect-VIServer -Server $vCenter -User $vcUser -Password VMware123 + + Write-Host "`nConnected to vCenter: " $vCenter + Write-Host 'Retrieving snapshot information...' + Write-Progress -Activity 'Working...' + + # Get VM snapshot information and output in table format + $getSnap = Get-VM | Get-Snapshot | sort SizeGB -descending | Select VM, Name, Created, @{Label="Size";Expression={"{0:N2} GB" -f ($_.SizeGB)}}, Id + $getSnap | Format-Table | Out-Default + +# Close connection to active vCenter +Disconnect-VIServer $vCenter -Confirm:$false + Write-Host 'Connection closed to' $vCenter +} +else { + Write-Warning "Error: No data entered for vCenter!" +} From 038742d872909068ff1384489d06c0679303f180 Mon Sep 17 00:00:00 2001 From: Chris Date: Tue, 26 Sep 2017 14:53:08 +0100 Subject: [PATCH 21/23] Update VMware.VMEncryption.psm1 Example for Get-Help was using $SetVMEncryptionKey rather than SetVMDiskEncryptionKey ($ sign and other command used) --- Modules/VMware.VMEncryption/VMware.VMEncryption.psm1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/VMware.VMEncryption/VMware.VMEncryption.psm1 b/Modules/VMware.VMEncryption/VMware.VMEncryption.psm1 index 8d19b50..e91b0c5 100644 --- a/Modules/VMware.VMEncryption/VMware.VMEncryption.psm1 +++ b/Modules/VMware.VMEncryption/VMware.VMEncryption.psm1 @@ -1034,7 +1034,7 @@ Function Set-VMDiskEncryptionKey { C:\PS>$KMSCluster = Get-KMSCluster | select -last 1 C:\PS>$VM = Get-VM -Name win2012 C:\PS>$HardDisk = get-vm $vm|Get-HardDisk - C:\PS>$HardDisk|$Set-VMEncryptionKey -VM $VM -KMSClusterId $KMSCluster.Id -Deep + C:\PS>$HardDisk| Set-VMDiskEncryptionKey -VM $VM -KMSClusterId $KMSCluster.Id -Deep Deep rekeys all the disks of the $VM using a new key. The key is generted from the KMS whose clusterId is $KMSCluster.Id. From 677d0c211d0006751b407e233d2f4dac34c6c2a2 Mon Sep 17 00:00:00 2001 From: William Lam Date: Wed, 4 Oct 2017 14:26:05 -0700 Subject: [PATCH 22/23] Adding VMware Fusion Module --- Modules/VMware.Hosted/VMware.Hosted.psd1 | 18 + Modules/VMware.Hosted/VMware.Hosted.psm1 | 501 +++++++++++++++++++++++ 2 files changed, 519 insertions(+) create mode 100644 Modules/VMware.Hosted/VMware.Hosted.psd1 create mode 100644 Modules/VMware.Hosted/VMware.Hosted.psm1 diff --git a/Modules/VMware.Hosted/VMware.Hosted.psd1 b/Modules/VMware.Hosted/VMware.Hosted.psd1 new file mode 100644 index 0000000..59d5b53 --- /dev/null +++ b/Modules/VMware.Hosted/VMware.Hosted.psd1 @@ -0,0 +1,18 @@ +@{ + ModuleToProcess = 'VMware.Hosted.psm1' + ModuleVersion = '1.0.0.0' + GUID = '11393D09-D6B8-4E79-B9BC-247F1BE66683' + Author = 'William Lam' + CompanyName = 'primp-industries.com' + Copyright = '(c) 2017. All rights reserved.' + Description = 'Powershell Module for VMware Fusion 10 REST API' + PowerShellVersion = '5.0' + FunctionsToExport = 'Get-HostedCommand','Connect-HostedServer','Disconnect-HostedServer','Get-HostedVM','Start-HostedVM','Stop-HostedVM','Suspend-HostedVM','Resume-HostedVM','New-HostedVM','Remove-HostedVM','Get-HostedVMSharedFolder','New-HostedVMSharedFolder','Remove-HostedVMSharedFolder','Get-HostedVMNic','Get-HostedNetworks' + PrivateData = @{ + PSData = @{ + Tags = @('Fusion','REST','vmrest') + LicenseUri = 'https://www.tldrlegal.com/l/mit' + ProjectUri = 'https://github.com/lamw/PowerCLI-Example-Scripts/tree/master/Modules/VMware.Hosted' + } + } +} \ No newline at end of file diff --git a/Modules/VMware.Hosted/VMware.Hosted.psm1 b/Modules/VMware.Hosted/VMware.Hosted.psm1 new file mode 100644 index 0000000..4997dee --- /dev/null +++ b/Modules/VMware.Hosted/VMware.Hosted.psm1 @@ -0,0 +1,501 @@ +Function Get-Confirmation { + $choice = "" + while ($choice -notmatch "[y|n]"){ + $choice = read-host "Do you want to continue? (Y/N)" + } + if($choice -ne 'y') { + break + } +} + +Function Connect-HostedServer { + Param ( + [parameter(Mandatory=$true,ValueFromPipeline=$true)][string]$Server, + [parameter(Mandatory=$true,ValueFromPipeline=$true)][string]$Username, + [parameter(Mandatory=$true,ValueFromPipeline=$true)][string]$Password, + [parameter(Mandatory=$false,ValueFromPipeline=$true)][string]$Protocol = "http", + [parameter(Mandatory=$false,ValueFromPipeline=$true)][int]$Port = 8697 + ) + $pair = $Username+":"+$Password + $bytes = [System.Text.Encoding]::ASCII.GetBytes($pair) + $base64 = [System.Convert]::ToBase64String($bytes) + $basicAuthValue = "Basic $base64" + $headers = @{Authorization = $basicAuthValue} + + $Global:DefaultHostedServer = [pscustomobject] @{ + Server=$Protocol + "://" + $server + ":$Port/api"; + Protcol=$Protocol + Headers=$headers + } + + if($DefaultHostedServer.Protcol -eq "https") { + # PowerShell Core has a nice -SkipCertificateCheck but looks like Windows does NOT :( + if($PSVersionTable.PSEdition -eq "Core") { + $Global:fusionCommand = "Invoke-Webrequest -SkipCertificateCheck " + } else { + # Needed for Windows PowerShell to handle HTTPS scenario + # https://stackoverflow.com/a/15627483 + $Provider = New-Object Microsoft.CSharp.CSharpCodeProvider + $Compiler = $Provider.CreateCompiler() + $Params = New-Object System.CodeDom.Compiler.CompilerParameters + $Params.GenerateExecutable = $false + $Params.GenerateInMemory = $true + $Params.IncludeDebugInformation = $false + $Params.ReferencedAssemblies.Add("System.DLL") > $null + $TASource=@' + namespace Local.ToolkitExtensions.Net.CertificatePolicy + { + public class TrustAll : System.Net.ICertificatePolicy + { + public bool CheckValidationResult(System.Net.ServicePoint sp,System.Security.Cryptography.X509Certificates.X509Certificate cert, System.Net.WebRequest req, int problem) + { + return true; + } + } + } +'@ + $TAResults=$Provider.CompileAssemblyFromSource($Params,$TASource) + $TAAssembly=$TAResults.CompiledAssembly + ## We create an instance of TrustAll and attach it to the ServicePointManager + $TrustAll = $TAAssembly.CreateInstance("Local.ToolkitExtensions.Net.CertificatePolicy.TrustAll") + [System.Net.ServicePointManager]::CertificatePolicy = $TrustAll + $Global:fusionCommand = "Invoke-Webrequest " + } + } else { + $Global:fusionCommand = "Invoke-Webrequest " + } + $Global:DefaultHostedServer +} + +Function Disconnect-HostedServer { + Param ( + [parameter(Mandatory=$true,ValueFromPipeline=$true)][string]$Server + ) + + $Global:DefaultHostedServer = $null +} + +Function Get-HostedVM { + Param ( + [parameter(Mandatory=$false,ValueFromPipeline=$true)][string]$Id + ) + + if(!$Global:DefaultHostedServer) { + Write-Host -ForegroundColor Red "You are not connected to Hosted Server, please run Connect-HostedServer" + exit + } + + if($Id) { + $vmUri = $Global:DefaultHostedServer.Server + "/vms/" + $Id + try { + $params = "-Headers `$Global:DefaultHostedServer.Headers -Uri $vmUri -Method GET -ContentType `"application/vnd.vmware.vmw.rest-v1+json`"" + $command = $Global:fusionCommand + $params + $vm = Invoke-Expression -Command $command | ConvertFrom-Json -ErrorAction Stop + } catch { + Write-host -ForegroundColor Red "Invalid VM Id $Id" + break + } + + $vmIPUri = $Global:DefaultHostedServer.Server + "/vms/" + $Id + "/ip" + try { + $params = "-UseBasicParsing -Headers `$Global:DefaultHostedServer.Headers -Uri $vmIPUri -Method GET -ContentType `"application/vnd.vmware.vmw.rest-v1+json`"" + $command = $Global:fusionCommand + $params + $vmIPResults = Invoke-Expression -Command $command | ConvertFrom-Json -ErrorAction Stop + $vmIP = $vmIPResults.ip + } catch { + $vmIP = "N/A" + } + + $vmPowerUri = $Global:DefaultHostedServer.Server + "/vms/" + $Id + "/power" + try { + $params = "-UseBasicParsing -Headers `$Global:DefaultHostedServer.Headers -Uri $vmPowerUri -Method GET -ContentType `"application/vnd.vmware.vmw.rest-v1+json`"" + $command = $Global:fusionCommand + $params + $vmPower = Invoke-Expression -Command $command | ConvertFrom-Json -ErrorAction Stop + } catch { + $vmPower = "N/A" + } + + $results = [pscustomobject] @{ + Id = $vm.Id; + CPU = $vm.Cpu.processors; + Memory = $vm.Memory; + PowerState = $vmPower.power_state; + IPAddress = $vmIP; + } + $results + } else { + $uri = $Global:DefaultHostedServer.Server + "/vms" + $params = "-UseBasicParsing -Headers `$Global:DefaultHostedServer.Headers -Uri $uri -Method GET -ContentType `"application/vnd.vmware.vmw.rest-v1+json`"" + $command = $Global:fusionCommand + $params + try { + Invoke-Expression -Command $command | ConvertFrom-Json -ErrorAction Stop + } catch { + Write-host -ForegroundColor Red "Failed to list VMs" + } + } +} + +Function Start-HostedVM { + Param ( + [parameter(Mandatory=$true,ValueFromPipeline=$true)][string]$Id + ) + + if(!$Global:DefaultHostedServer) { + Write-Host -ForegroundColor Red "You are not connected to Hosted Server, please run Connect-HostedServer" + exit + } + + $vmPowerUri = $Global:DefaultHostedServer.Server + "/vms/" + $Id + "/power" + try { + $params = "-UseBasicParsing -Headers `$Global:DefaultHostedServer.Headers -Uri $vmPowerUri -Method GET -ContentType `"application/vnd.vmware.vmw.rest-v1+json`"" + $command = $Global:fusionCommand + $params + $vmPower = Invoke-Expression -Command $command | ConvertFrom-Json -ErrorAction Stop + } catch { + Write-host -ForegroundColor Red "Invalid VM Id $Id" + break + } + + if($vmPower.power_state -eq "poweredOff" -or $vmPower.power_state -eq "suspended") { + try { + Write-Host "Powering on VM $Id ..." + $params = "-UseBasicParsing -Headers `$Global:DefaultHostedServer.Headers -Uri $vmPowerUri -Method PUT -ContentType `"application/vnd.vmware.vmw.rest-v1+json`" -Body `"on`"" + $command = $Global:fusionCommand + $params + $vm = Invoke-Expression -Command $command | ConvertFrom-Json -ErrorAction Stop + } catch { + Write-host -ForegroundColor Red "Unable to Power On VM $Id" + break + } + } else { + Write-Host "VM $Id is already Powered On" + } +} + +Function Stop-HostedVM { + Param ( + [parameter(Mandatory=$true,ValueFromPipeline=$true)][string]$Id, + [parameter(Mandatory=$false,ValueFromPipeline=$true)][boolean]$Soft, + [parameter(Mandatory=$false,ValueFromPipeline=$true)][boolean]$Confirm = $true + ) + + if(!$Global:DefaultHostedServer) { + Write-Host -ForegroundColor Red "You are not connected to Hosted Server, please run Connect-HostedServer" + exit + } + + if($Confirm) { + Get-Confirmation + } + + $vmPowerUri = $Global:DefaultHostedServer.Server + "/vms/" + $Id + "/power" + try { + $params += "-UseBasicParsing -Headers `$Global:DefaultHostedServer.Headers -Uri $vmPowerUri -Method GET -ContentType `"application/vnd.vmware.vmw.rest-v1+json`"" + $command = $Global:fusionCommand + $params + $vmPower = Invoke-Expression -Command $command | ConvertFrom-Json -ErrorAction Stop + } catch { + Write-host -ForegroundColor Red "Invalid VM Id $Id" + break + } + + if($vmPower.power_state -eq "poweredOn") { + if($Soft) { + try { + Write-Host "Shutting down VM $Id ..." + $params = "-UseBasicParsing -Headers `$Global:DefaultHostedServer.Headers -Uri $vmPowerUri -Method PUT -ContentType `"application/vnd.vmware.vmw.rest-v1+json`" -Body `"shutdown`"" + $command = $Global:fusionCommand + $params + $vm = Invoke-Expression -Command $command | ConvertFrom-Json -ErrorAction Stop + } catch { + Write-host -ForegroundColor Red "Unable to Shutdown VM $Id" + break + } + } else { + try { + Write-Host "Powering off VM $Id ..." + $params = "-UseBasicParsing -Headers `$Global:DefaultHostedServer.Headers -Uri $vmPowerUri -Method PUT -ContentType `"application/vnd.vmware.vmw.rest-v1+json`" -Body `"off`"" + $command = $Global:fusionCommand + $params + $vm = Invoke-Expression -Command $command | ConvertFrom-Json -ErrorAction Stop + } catch { + Write-host -ForegroundColor Red "Unable to Power Off VM $Id" + break + } + } + } else { + Write-Host "VM $Id is already Powered Off" + } +} + +Function Suspend-HostedVM { + Param ( + [parameter(Mandatory=$true,ValueFromPipeline=$true)][string]$Id, + [parameter(Mandatory=$false,ValueFromPipeline=$true)][boolean]$Confirm + ) + + if(!$Global:DefaultHostedServer) { + Write-Host -ForegroundColor Red "You are not connected to Hosted Server, please run Connect-HostedServer" + exit + } + + if($Confirm) { + Get-Confirmation + } + + $vmPowerUri = $Global:DefaultHostedServer.Server + "/vms/" + $Id + "/power" + try { + $params = "-UseBasicParsing -Headers `$Global:DefaultHostedServer.Headers -Uri $vmPowerUri -Method GET -ContentType `"application/vnd.vmware.vmw.rest-v1+json`"" + $command = $Global:fusionCommand + $params + $vmPower = Invoke-Expression -Command $command | ConvertFrom-Json -ErrorAction Stop + } catch { + Write-host -ForegroundColor Red "Invalid VM Id $Id" + break + } + + if($vmPower.power_state -eq "poweredOn") { + try { + Write-Host "Suspending VM $Id ..." + $params = "-UseBasicParsing -Headers `$Global:DefaultHostedServer.Headers -Uri $vmPowerUri -Method PUT -ContentType `"application/vnd.vmware.vmw.rest-v1+json`" -Body `"suspend`"" + $command = $Global:fusionCommand + $params + $vm = Invoke-Expression -Command $command | ConvertFrom-Json -ErrorAction Stop + } catch { + Write-host -ForegroundColor Red "Unable to suspend VM $Id" + break + } + } else { + Write-Host "VM $Id can not be suspended because it is either Powered Off or Suspended" + } +} + +Function Resume-HostedVM { + Param ( + [parameter(Mandatory=$true,ValueFromPipeline=$true)][string]$Id + ) + + if(!$Global:DefaultHostedServer) { + Write-Host -ForegroundColor Red "You are not connected to Hosted Server, please run Connect-HostedServer" + exit + } + + $vmPowerUri = $Global:DefaultHostedServer.Server + "/vms/" + $Id + "/power" + try { + $params = "-UseBasicParsing -Headers `$Global:DefaultHostedServer.Headers -Uri $vmPowerUri -Method GET -ContentType `"application/vnd.vmware.vmw.rest-v1+json`"" + $command = $Global:fusionCommand + $params + $vmPower = Invoke-Expression -Command $command | ConvertFrom-Json -ErrorAction Stop + } catch { + Write-host -ForegroundColor Red "Invalid VM Id $Id" + break + } + + if($vmPower.power_state -eq "suspended") { + try { + Start-HostedVM -Id $Id + } catch { + Write-host -ForegroundColor Red "Unable to Resume VM $Id" + break + } + } else { + Write-Host "VM $Id is not Suspended" + } +} + +Function New-HostedVM { + Param ( + [parameter(Mandatory=$true,ValueFromPipeline=$true)][string]$ParentId, + [parameter(Mandatory=$true,ValueFromPipeline=$true)][string]$Name + ) + + if(!$Global:DefaultHostedServer) { + Write-Host -ForegroundColor Red "You are not connected to Hosted Server, please run Connect-HostedServer" + exit + } + + $vm = Get-HostedVM -Id $ParentId + + if($vm -match "Invalid VM Id") { + Write-host -ForegroundColor Red "Unable to find existing VM Id $ParentId" + break + } + + $vmUri = $Global:DefaultHostedServer.Server + "/vms" + $body = @{"ParentId"="$ParentId";"Name"=$Name} + $body = $body | ConvertTo-Json + + try { + Write-Host "Cloning VM $ParentId to $Name ..." + $params = "-UseBasicParsing -Headers `$Global:DefaultHostedServer.Headers -Uri $vmUri -Method POST -ContentType `"application/vnd.vmware.vmw.rest-v1+json`" -Body `$body" + $command = $Global:fusionCommand + $params + Invoke-Expression -Command $command | ConvertFrom-Json -ErrorAction Stop + } catch { + Write-host -ForegroundColor Red "Failed to Clone VM Id $ParentId" + break + } +} + +Function Remove-HostedVM { + Param ( + [parameter(Mandatory=$true,ValueFromPipeline=$true)][string]$Id, + [parameter(Mandatory=$false,ValueFromPipeline=$true)][boolean]$Confirm = $true + ) + + if(!$Global:DefaultHostedServer) { + Write-Host -ForegroundColor Red "You are not connected to Hosted Server, please run Connect-HostedServer" + exit + } + + $vm = Get-HostedVM -Id $Id + + if($vm -match "Invalid VM Id") { + Write-host -ForegroundColor Red "Unable to find existing VM Id $Id" + break + } + + if($Confirm) { + Get-Confirmation + } + + $vmUri = $Global:DefaultHostedServer.Server + "/vms/" + $Id + try { + Write-Host "Deleting VM Id $Id ..." + $params = "-UseBasicParsing -Headers `$Global:DefaultHostedServer.Headers -Uri $vmUri -Method DELETE -ContentType `"application/vnd.vmware.vmw.rest-v1+json`"" + $command = $Global:fusionCommand + $params + Invoke-Expression -Command $command | ConvertFrom-Json -ErrorAction Stop + } catch { + Write-host -ForegroundColor Red "Failed to Delete VM Id $Id" + break + } +} + +Function Get-HostedVMSharedFolder { + Param ( + [parameter(Mandatory=$true,ValueFromPipeline=$true)][string]$Id + ) + + if(!$Global:DefaultHostedServer) { + Write-Host -ForegroundColor Red "You are not connected to Hosted Server, please run Connect-HostedServer" + exit + } + + $folderUri = $Global:DefaultHostedServer.Server + "/vms/" + $Id + "/sharedfolders" + try { + $params = "-UseBasicParsing -Headers `$Global:DefaultHostedServer.Headers -Uri $folderUri -Method GET -ContentType `"application/vnd.vmware.vmw.rest-v1+json`"" + $command = $Global:fusionCommand + $params + Invoke-Expression -Command $command | ConvertFrom-Json -ErrorAction Stop + } catch { + Write-host -ForegroundColor Red "Invalid VM Id $Id" + break + } +} + +Function New-HostedVMSharedFolder { + Param ( + [parameter(Mandatory=$true,ValueFromPipeline=$true)][string]$Id, + [parameter(Mandatory=$true,ValueFromPipeline=$true)][string]$FolderName, + [parameter(Mandatory=$true,ValueFromPipeline=$true)][string]$HostPath + ) + + if(!$Global:DefaultHostedServer) { + Write-Host -ForegroundColor Red "You are not connected to Hosted Server, please run Connect-HostedServer" + exit + } + + $body = @{"folder_id"="$FolderName";"host_path"=$HostPath;"flags"=4} + $body = $body | ConvertTo-Json + + $folderUri = $Global:DefaultHostedServer.Server + "/vms/" + $Id + "/sharedfolders" + try { + Write-Host "Creating new Shared Folder $FolderName to $HostPath for VM Id $Id ..." + $params += "-UseBasicParsing -Headers `$Global:DefaultHostedServer.Headers -Uri $folderUri -Method POST -ContentType `"application/vnd.vmware.vmw.rest-v1+json`" -Body `$body" + $command = $Global:fusionCommand + $params + Invoke-Expression -Command $command | ConvertFrom-Json -ErrorAction Stop + } catch { + Write-host -ForegroundColor Red "Failed to create Shared Folder for VM Id $Id" + break + } +} + +Function Remove-HostedVMSharedFolder { + Param ( + [parameter(Mandatory=$true,ValueFromPipeline=$true)][string]$Id, + [parameter(Mandatory=$true,ValueFromPipeline=$true)][string]$FolderName, + [parameter(Mandatory=$false,ValueFromPipeline=$true)][boolean]$Confirm = $true + ) + + if(!$Global:DefaultHostedServer) { + Write-Host -ForegroundColor Red "You are not connected to Hosted Server, please run Connect-HostedServer" + exit + } + + if($Confirm) { + Get-Confirmation + } + + $folderUri = $Global:DefaultHostedServer.Server + "/vms/" + $Id + "/sharedfolders/" + $FolderName + try { + Write-Host "Removing Shared Folder $FolderName for VM Id $Id ..." + $params += "-UseBasicParsing -Headers `$Global:DefaultHostedServer.Headers -Uri $folderUri -Method DELETE -ContentType `"application/vnd.vmware.vmw.rest-v1+json`"" + $command = $Global:fusionCommand + $params + Invoke-Expression -Command $command | ConvertFrom-Json -ErrorAction Stop + } catch { + Write-host -ForegroundColor Red "Failed to remove Shared Folder for VM Id $Id" + break + } +} + +Function Get-HostedVMNic { + Param ( + [parameter(Mandatory=$true,ValueFromPipeline=$true)][string]$Id + ) + + if(!$Global:DefaultHostedServer) { + Write-Host -ForegroundColor Red "You are not connected to Hosted Server, please run Connect-HostedServer" + exit + } + + $vmNicUri = $Global:DefaultHostedServer.Server + "/vms/" + $Id + "/nic" + try { + $params += "-UseBasicParsing -Headers `$Global:DefaultHostedServer.Headers -Uri $vmNicUri -Method GET -ContentType `"application/vnd.vmware.vmw.rest-v1+json`"" + $command = $Global:fusionCommand + $params + $vmNics = Invoke-Expression -Command $command | ConvertFrom-Json -ErrorAction Stop + } catch { + Write-host -ForegroundColor Red "Invalid VM Id $Id" + break + } + + $results = @() + foreach ($vmNic in $vmNics.nics) { + $tmp = [pscustomobject] @{ + Index = $vmNic.index; + Type = $vmNic.Type; + VMnet = $vmNic.Vmnet; + } + $results+=$tmp + } + $results +} + +Function Get-HostedNetworks { + if(!$Global:DefaultHostedServer) { + Write-Host -ForegroundColor Red "You are not connected to Hosted Server, please run Connect-HostedServer" + exit + } + + $networksUri = $Global:DefaultHostedServer.Server + "/vmnet" + try { + $params = "-UseBasicParsing -Headers `$Global:DefaultHostedServer.Headers -Uri $networksUri -Method GET -ContentType `"application/vnd.vmware.vmw.rest-v1+json`"" + $command = $Global:fusionCommand + $params + $networks = Invoke-Expression -Command $command | ConvertFrom-Json -ErrorAction Stop + } catch { + Write-host -ForegroundColor Red "Unable to retrieve Networks" + break + } + + $results = @() + foreach ($network in $networks.vmnets) { + $tmp = [pscustomobject] @{ + Name = $network.Name; + Type = $network.Type; + DHCP = $network.Dhcp; + Network = $network.subnet; + Netmask = $network.mask; + } + $results+=$tmp + } + $results +} \ No newline at end of file From 04aabda1bab5e19d7d27f4ccbd61c40d3ef73ca0 Mon Sep 17 00:00:00 2001 From: mtelvers Date: Mon, 9 Oct 2017 12:10:33 +0100 Subject: [PATCH 23/23] Updated Set-HVPool and add function Set-HVGlobalEntitlement Add -globalEntitlement parameter to Set-HVPool to allow pool to be associated with a globalentitlement. Previously this was only possible through the New-HVPool cmdlet. Added cmdlet Set-HVGlobalEntitlement to allow global entitlements to be updated --- .../VMware.Hv.Helper/VMware.HV.Helper.psm1 | 227 +++++++++++++++++- 1 file changed, 224 insertions(+), 3 deletions(-) diff --git a/Modules/VMware.Hv.Helper/VMware.HV.Helper.psm1 b/Modules/VMware.Hv.Helper/VMware.HV.Helper.psm1 index e335ce2..058f979 100644 --- a/Modules/VMware.Hv.Helper/VMware.HV.Helper.psm1 +++ b/Modules/VMware.Hv.Helper/VMware.HV.Helper.psm1 @@ -1,5 +1,5 @@ #Script Module : VMware.Hv.Helper -#Version : 1.1 +#Version : 1.2 #Copyright © 2016 VMware, Inc. All Rights Reserved. @@ -5776,7 +5776,8 @@ function Set-HVPool { .NOTES Author : Praveen Mathamsetty. Author email : pmathamsetty@vmware.com - Version : 1.1 + Version : 1.2 + Updated : Mark Elvers ===Tested Against Environment==== Horizon View Server Version : 7.0.2, 7.1.0 @@ -5818,6 +5819,16 @@ function Set-HVPool { [Parameter(Mandatory = $false)] [string]$Spec, + [Parameter(Mandatory = $false)] + [string] + $globalEntitlement, + + [Parameter(Mandatory = $false)] + [boolean]$allowUsersToChooseProtocol, + + [Parameter(Mandatory = $false)] + [boolean]$enableHTMLAccess, + [Parameter(Mandatory = $false)] $HvServer = $null ) @@ -5906,6 +5917,36 @@ function Set-HVPool { $updates += Get-MapEntry -key 'automatedDesktopData.virtualCenterProvisioningSettings.enableProvisioning' ` -value $false } + + if ($PSBoundParameters.ContainsKey("allowUsersToChooseProtocol")) { + $updates += Get-MapEntry -key 'desktopSettings.displayProtocolSettings.allowUsersToChooseProtocol' -value $allowUsersToChooseProtocol + } + + if ($PSBoundParameters.ContainsKey("enableHTMLAccess")) { + $updates += Get-MapEntry -key 'desktopSettings.displayProtocolSettings.enableHTMLAccess' -value $enableHTMLAccess + } + + $info = $services.PodFederation.PodFederation_get() + if ($globalEntitlement -and ("ENABLED" -eq $info.localPodStatus.status)) { + $QueryFilterEquals = New-Object VMware.Hv.QueryFilterEquals + $QueryFilterEquals.memberName = 'base.displayName' + $QueryFilterEquals.value = $globalEntitlement + $defn = New-Object VMware.Hv.QueryDefinition + $defn.queryEntityType = 'GlobalEntitlementSummaryView' + $defn.Filter = $QueryFilterEquals + $query_service_helper = New-Object VMware.Hv.QueryServiceService + try { + $queryResults = $query_service_helper.QueryService_Query($services,$defn) + $globalEntitlementid = $queryResults.Results.id + if ($globalEntitlementid.length -eq 1) { + $updates += Get-MapEntry -key 'globalEntitlementData.globalEntitlement' -value $globalEntitlementid + } + } + catch { + Write-Host "GlobalEntitlement " $_ + } + } + $desktop_helper = New-Object VMware.Hv.DesktopService foreach ($item in $poolList.Keys) { Write-Host "Updating the Pool: " $poolList.$item @@ -8597,6 +8638,185 @@ function Get-HVGlobalEntitlement { } +function Set-HVGlobalEntitlement { +<# +.SYNOPSIS + Sets the existing pool properties. + +.DESCRIPTION + This cmdlet allows user to edit global entitlements. + +.PARAMETER DisplayName + Display Name of Global Entitlement. + +.PARAMETER Description + Description of Global Entitlement. + +.PARAMETER EnableHTMLAccess + If set to true, the desktops that are associated with this GlobalEntitlement must also have HTML Access enabled. + +.PARAMETER Key + Property names path separated by . (dot) from the root of desktop spec. + +.PARAMETER Value + Property value corresponds to above key name. + +.PARAMETER HvServer + View API service object of Connect-HVServer cmdlet. + +.PARAMETER Spec + Path of the JSON specification file containing key/value pair. + +.EXAMPLE + Set-HVGlobalEntitlement -DisplayName 'MyGlobalEntitlement' -Spec 'C:\Edit-HVPool\EditPool.json' -Confirm:$false + Updates pool configuration by using json file + +.EXAMPLE + Set-HVGlobalEntitlement -DisplayName 'MyGlobalEntitlement' -Key 'base.description' -Value 'update description' + Updates pool configuration with given parameters key and value + +.EXAMPLE + Set-HVGlobalEntitlement -DisplayName 'MyGlobalEntitlement' -enableHTMLAccess $true + Set Allow HTML Access on a global entitlement. Note that it must also be enabled on the Pool and as of 7.3.0 Allow User to Choose Protocol must be enabled (which is unfortunately read-only) + +.EXAMPLE + Get-HVGlobalEntitlement | Set-HVGlobalEntitlement -Disable + Disable all global entitlements + +.OUTPUTS + None + +.NOTES + Author : Mark Elvers + Author email : mark.elvers@tunbury.org + Version : 1.0 + + ===Tested Against Environment==== + Horizon View Server Version : 7.3.0, 7.3.1 + PowerCLI Version : PowerCLI 6.5.1 + PowerShell Version : 5.0 +#> + + [CmdletBinding( + SupportsShouldProcess = $true, + ConfirmImpact = 'High' + )] + + param( + [Parameter(Mandatory = $true,ParameterSetName = 'option')] + [string] $displayName, + + [Parameter(ValueFromPipeline = $true,ParameterSetName = 'pipeline')] + $GlobalEntitlements, + + [Parameter(Mandatory = $false)] + [string]$Key, + + [Parameter(Mandatory = $false)] + $Value, + + [Parameter(Mandatory = $false)] + [string]$Spec, + + [Parameter(Mandatory = $false)] + [switch]$Enable, + + [Parameter(Mandatory = $false)] + [switch]$Disable, + + [Parameter(Mandatory = $false)] + [boolean]$enableHTMLAccess, + + [Parameter(Mandatory = $false)] + $HvServer = $null + ) + + begin { + $services = Get-ViewAPIService -hvServer $hvServer + if ($null -eq $services) { + Write-Error "Could not retrieve ViewApi services from connection object" + break + } + } + + process { + $info = $services.PodFederation.PodFederation_get() + if ("ENABLED" -ne $info.localPodStatus.status) { + Write-Host "Multi-DataCenter-View/CPA is not enabled" + return + } + + $confirmFlag = Get-HVConfirmFlag -keys $PsBoundParameters.Keys + $geList = @{} + if ($displayName) { + try { + $ge = Get-HVGlobalEntitlement -displayName $displayName -suppressInfo $true -hvServer $hvServer + } catch { + Write-Error "Make sure Get-HVGlobalEntitlement advanced function is loaded, $_" + break + } + if ($ge) { + $geList.add($ge.id, $ge.base.DisplayName) + } else { + Write-Error "No globalentitlement found with name: [$displayName]" + break + } + } elseif ($PSCmdlet.MyInvocation.ExpectingInput -or $GlobalEntitlements) { + foreach ($item in $GlobalEntitlements) { + if ($item.GetType().name -eq 'GlobalEntitlementSummaryView') { + $geList.add($item.id, $item.Base.DisplayName) + } else { + Write-Error "In pipeline did not get object of expected type GlobalEntitlementSummaryView" + [System.gc]::collect() + return + } + } + } + + $updates = @() + if ($key -and $value) { + $updates += Get-MapEntry -key $key -value $value + } elseif ($key -or $value) { + Write-Error "Both key:[$key] and value:[$value] needs to be specified" + } + if ($spec) { + try { + $specObject = Get-JsonObject -specFile $spec + } catch { + Write-Error "Json file exception, $_" + return + } + foreach ($member in ($specObject.PSObject.Members | Where-Object { $_.MemberType -eq 'NoteProperty' })) { + $updates += Get-MapEntry -key $member.name -value $member.value + } + } + + if ($Enable) { + $updates += Get-MapEntry -key 'base.enabled' -value $true + } + elseif ($Disable) { + $updates += Get-MapEntry -key 'base.enabled' -value $false + } + + if ($PSBoundParameters.ContainsKey("enableHTMLAccess")) { + $updates += Get-MapEntry -key 'base.enableHTMLAccess' -value $enableHTMLAccess + } + + $ge_helper = New-Object VMware.HV.GlobalEntitlementService + foreach ($item in $geList.Keys) { + Write-Host "Updating the Entitlement: " $geList.$item + if (!$confirmFlag -OR $pscmdlet.ShouldProcess($geList.$item)) { + $ge_helper.GlobalEntitlement_Update($services, $item, $updates) + } + } + } + + end { + [System.gc]::collect() + } +} + + function Remove-HVGlobalEntitlement { <# @@ -9371,4 +9591,5 @@ function Set-HVGlobalSettings { } } -Export-ModuleMember Add-HVDesktop,Add-HVRDSServer,Connect-HVEvent,Disconnect-HVEvent,Get-HVPoolSpec,Get-HVInternalName, Get-HVEvent,Get-HVFarm,Get-HVFarmSummary,Get-HVPool,Get-HVPoolSummary,Get-HVMachine,Get-HVMachineSummary,Get-HVQueryResult,Get-HVQueryFilter,New-HVFarm,New-HVPool,Remove-HVFarm,Remove-HVPool,Set-HVFarm,Set-HVPool,Start-HVFarm,Start-HVPool,New-HVEntitlement,Get-HVEntitlement,Remove-HVEntitlement, Set-HVMachine, New-HVGlobalEntitlement, Remove-HVGlobalEntitlement, Get-HVGlobalEntitlement, Get-HVPodSession, Set-HVApplicationIcon, Remove-HVApplicationIcon, Get-HVGlobalSettings, Set-HVGlobalSettings +Export-ModuleMember Add-HVDesktop,Add-HVRDSServer,Connect-HVEvent,Disconnect-HVEvent,Get-HVPoolSpec,Get-HVInternalName, Get-HVEvent,Get-HVFarm,Get-HVFarmSummary,Get-HVPool,Get-HVPoolSummary,Get-HVMachine,Get-HVMachineSummary,Get-HVQueryResult,Get-HVQueryFilter,New-HVFarm,New-HVPool,Remove-HVFarm,Remove-HVPool,Set-HVFarm,Set-HVPool,Start-HVFarm,Start-HVPool,New-HVEntitlement,Get-HVEntitlement,Remove-HVEntitlement, Set-HVMachine, New-HVGlobalEntitlement, Remove-HVGlobalEntitlement, Get-HVGlobalEntitlement, Get-HVPodSession, Set-HVApplicationIcon, Remove-HVApplicationIcon, Get-HVGlobalSettings, Set-HVGlobalSettings, Set-HVGlobalEntitlement +