1175 lines
86 KiB
PowerShell
1175 lines
86 KiB
PowerShell
#region Publish-RackpadDesiredState
|
|
Function Publish-RackpadDesiredState
|
|
{
|
|
<#
|
|
.SYNOPSIS
|
|
Applies a desired state graph to a Rackpad instance in dependency order.
|
|
|
|
.DESCRIPTION
|
|
Publication proceeds in phases so that every referenced object exists before the object that references it is written. Each
|
|
phase resolves natural keys to Rackpad identifiers using lookup tables that are seeded from the pre-loaded inventory and then
|
|
extended as objects are created.
|
|
|
|
Phase order.
|
|
|
|
01 Lab The lab is created when it does not already exist
|
|
02 VLANs VLANs are written first because ports, subnets, and SSIDs reference them
|
|
03 Rooms Rooms are written before racks
|
|
04 Racks Racks reference rooms
|
|
05 Physical devices Devices placed within a rack or a room, which have no device parent
|
|
06 Virtual devices Devices parented to a host, which requires the host to already exist
|
|
07 Virtual switches Bridges hosted by a physical device
|
|
08 Ports Ports reference their device, their VLAN, and optionally a virtual switch
|
|
09 Subnets Subnets reference VLANs
|
|
10 IP zones Address ranges that serve each room and rack, declared in the settings document
|
|
11 IP assignments Assignments reference a subnet and a device
|
|
12 Cable links Links reference two ports
|
|
13 Wireless Controllers, SSIDs, access points, radios, and client associations
|
|
|
|
DHCP scopes are never written. Every operation honors the common WhatIf parameter.
|
|
|
|
.PARAMETER Session
|
|
The session object produced by Connect-RackpadInstance.
|
|
|
|
.PARAMETER DesiredState
|
|
The desired state graph produced by ConvertTo-RackpadDesiredState.
|
|
|
|
.PARAMETER Inventory
|
|
The pre-loaded inventory produced by Get-RackpadInventory.
|
|
|
|
.PARAMETER ManagedState
|
|
The state object produced by Get-RackpadManagedState.
|
|
|
|
.PARAMETER LabID
|
|
The identifier of the lab that will receive the desired state.
|
|
|
|
.PARAMETER Options
|
|
The processing options object taken from the instance configuration.
|
|
|
|
.EXAMPLE
|
|
$PublicationSummary = Publish-RackpadDesiredState -Session $RackpadSession -DesiredState $DesiredState -Inventory $RackpadInventory -ManagedState $ManagedState -LabID $LabID -Options $InstanceConfiguration.Options
|
|
|
|
.NOTES
|
|
Author: Grace Solutions
|
|
Version: 2026.08.02.0000
|
|
#>
|
|
|
|
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '', Justification = 'Every write is delegated to Sync-RackpadObject, which calls ShouldProcess. SupportsShouldProcess is declared here so that WhatIf propagates down the call chain.')]
|
|
[CmdletBinding(SupportsShouldProcess=$True)]
|
|
Param
|
|
(
|
|
[Parameter(Mandatory=$True)]
|
|
[ValidateNotNullOrEmpty()]
|
|
[PSObject]$Session,
|
|
|
|
[Parameter(Mandatory=$True)]
|
|
[ValidateNotNullOrEmpty()]
|
|
[PSObject]$DesiredState,
|
|
|
|
[Parameter(Mandatory=$True)]
|
|
[ValidateNotNullOrEmpty()]
|
|
[PSObject]$Inventory,
|
|
|
|
[Parameter(Mandatory=$True)]
|
|
[ValidateNotNullOrEmpty()]
|
|
[PSObject]$ManagedState,
|
|
|
|
[Parameter(Mandatory=$True)]
|
|
[ValidateNotNullOrEmpty()]
|
|
[String]$LabID,
|
|
|
|
[Parameter(Mandatory=$True)]
|
|
[ValidateNotNullOrEmpty()]
|
|
[PSObject]$Options
|
|
)
|
|
|
|
Try
|
|
{
|
|
$SyncResultList = New-Object -TypeName 'System.Collections.Generic.List[PSObject]'
|
|
|
|
#region Define the identifier lookup tables
|
|
$RoomIdentifierTable = New-Object -TypeName 'System.Collections.Generic.Dictionary[[System.String], [System.String]]'
|
|
$RackIdentifierTable = New-Object -TypeName 'System.Collections.Generic.Dictionary[[System.String], [System.String]]'
|
|
$DeviceIdentifierTable = New-Object -TypeName 'System.Collections.Generic.Dictionary[[System.String], [System.String]]'
|
|
$VirtualSwitchIdentifierTable = New-Object -TypeName 'System.Collections.Generic.Dictionary[[System.String], [System.String]]'
|
|
$PortIdentifierTable = New-Object -TypeName 'System.Collections.Generic.Dictionary[[System.String], [System.String]]'
|
|
$VLANIdentifierTable = New-Object -TypeName 'System.Collections.Generic.Dictionary[[System.String], [System.String]]'
|
|
$SubnetIdentifierTable = New-Object -TypeName 'System.Collections.Generic.Dictionary[[System.String], [System.String]]'
|
|
$SSIDIdentifierTable = New-Object -TypeName 'System.Collections.Generic.Dictionary[[System.String], [System.String]]'
|
|
$WirelessControllerIdentifierTable = New-Object -TypeName 'System.Collections.Generic.Dictionary[[System.String], [System.String]]'
|
|
$WirelessRadioIdentifierTable = New-Object -TypeName 'System.Collections.Generic.Dictionary[[System.String], [System.String]]'
|
|
|
|
ForEach ($ExistingRoom In $Inventory.RoomList) {$RoomIdentifierTable["$($ExistingRoom.name)".ToLowerInvariant()] = "$($ExistingRoom.id)"}
|
|
ForEach ($ExistingRack In $Inventory.RackList) {$RackIdentifierTable["$($ExistingRack.name)".ToLowerInvariant()] = "$($ExistingRack.id)"}
|
|
ForEach ($ExistingDevice In $Inventory.DeviceList) {$DeviceIdentifierTable["$($ExistingDevice.hostname)".ToLowerInvariant()] = "$($ExistingDevice.id)"}
|
|
ForEach ($ExistingVLAN In $Inventory.VLANList) {$VLANIdentifierTable["$($ExistingVLAN.vlanId)"] = "$($ExistingVLAN.id)"}
|
|
ForEach ($ExistingSubnet In $Inventory.SubnetList) {$SubnetIdentifierTable["$($ExistingSubnet.cidr)".ToLowerInvariant()] = "$($ExistingSubnet.id)"}
|
|
ForEach ($ExistingSSID In $Inventory.WirelessSSIDList) {$SSIDIdentifierTable["$($ExistingSSID.name)".ToLowerInvariant()] = "$($ExistingSSID.id)"}
|
|
ForEach ($ExistingController In $Inventory.WirelessControllerList) {$WirelessControllerIdentifierTable["$($ExistingController.name)".ToLowerInvariant()] = "$($ExistingController.id)"}
|
|
|
|
ForEach ($ExistingVirtualSwitch In $Inventory.VirtualSwitchList)
|
|
{
|
|
$HostDevice = $Inventory.DeviceList | Where-Object {("$($_.id)" -ieq "$($ExistingVirtualSwitch.hostDeviceId)")} | Select-Object -First 1
|
|
|
|
$VirtualSwitchIdentifierTable["$($HostDevice.hostname)|$($ExistingVirtualSwitch.name)".ToLowerInvariant()] = "$($ExistingVirtualSwitch.id)"
|
|
}
|
|
|
|
ForEach ($ExistingPort In $Inventory.PortList)
|
|
{
|
|
$PortDevice = $Inventory.DeviceList | Where-Object {("$($_.id)" -ieq "$($ExistingPort.deviceId)")} | Select-Object -First 1
|
|
|
|
$PortIdentifierTable["$($PortDevice.hostname)|$($ExistingPort.name)".ToLowerInvariant()] = "$($ExistingPort.id)"
|
|
}
|
|
|
|
ForEach ($ExistingRadio In $Inventory.WirelessRadioList)
|
|
{
|
|
$RadioDevice = $Inventory.DeviceList | Where-Object {("$($_.id)" -ieq "$($ExistingRadio.apDeviceId)")} | Select-Object -First 1
|
|
|
|
$WirelessRadioIdentifierTable["$($RadioDevice.hostname)|$($ExistingRadio.slotName)".ToLowerInvariant()] = "$($ExistingRadio.id)"
|
|
}
|
|
#endregion
|
|
|
|
#region Define the subnet containment logic used to attach IP assignments
|
|
[ScriptBlock]$TestIPAddressWithinCIDR = {
|
|
Param
|
|
(
|
|
[String]$IPAddress,
|
|
[String]$CIDR
|
|
)
|
|
|
|
$IsWithinCIDR = $False
|
|
|
|
Switch (([String]::IsNullOrWhiteSpace($IPAddress) -eq $False) -and ("$($CIDR)".Contains('/') -eq $True))
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$IsWithinCIDR = Try {[Boolean](Test-SubnetMember -SubjectIPAddress "$($IPAddress)" -ObjectIPAddress "$($CIDR)" -ErrorAction Stop)} Catch {$False}
|
|
}
|
|
}
|
|
|
|
Write-Output -InputObject ($IsWithinCIDR)
|
|
}
|
|
#endregion
|
|
|
|
#region Define the phase progress reporting logic
|
|
$PhaseNumber = 0
|
|
$TotalPhaseCount = 13
|
|
|
|
[ScriptBlock]$ReportPhaseProgress = {
|
|
Param
|
|
(
|
|
[String]$PhaseName,
|
|
[Int]$ItemCount
|
|
)
|
|
|
|
$WriteProgressParameters = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary'
|
|
$WriteProgressParameters.Activity = "Publishing the desired state to lab `"$($DesiredState.LabName)`""
|
|
$WriteProgressParameters.Status = "Phase $($PhaseNumber) of $($TotalPhaseCount): $($PhaseName) ($($ItemCount) object(s))"
|
|
$WriteProgressParameters.PercentComplete = [System.Math]::Round(($PhaseNumber / $TotalPhaseCount) * 100, 2)
|
|
$WriteProgressParameters.Id = 40
|
|
|
|
$Null = Write-Progress @WriteProgressParameters
|
|
|
|
Write-Verbose -Message "[$([System.DateTime]::UtcNow.ToString('yyyy/MM/dd HH:mm:ss.FFF'))] - [INFO] - Phase $($PhaseNumber) of $($TotalPhaseCount): $($PhaseName). [$($ItemCount) object(s)]"
|
|
}
|
|
#endregion
|
|
|
|
#region Phase 02 - VLANs
|
|
$PhaseNumber = 2
|
|
|
|
$Null = $ReportPhaseProgress.InvokeReturnAsIs('VLANs', $DesiredState.VLANList.Count)
|
|
|
|
ForEach ($DesiredVLAN In $DesiredState.VLANList)
|
|
{
|
|
$ExistingVLAN = $Inventory.VLANList | Where-Object {([Int]"0$($_.vlanId)" -eq [Int]"0$($DesiredVLAN.VLANID)")} | Select-Object -First 1
|
|
|
|
$VLANProperty = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary'
|
|
$VLANProperty.labId = "$($LabID)"
|
|
$VLANProperty.vlanId = [Int]"$($DesiredVLAN.VLANID)"
|
|
$VLANProperty.name = "$($DesiredVLAN.Name)"
|
|
$VLANProperty.description = "$($DesiredVLAN.Description)"
|
|
$VLANProperty.color = "$($DesiredVLAN.Color)"
|
|
|
|
$SyncResult = Sync-RackpadObject -Session ($Session) -EntityType 'vlan' -CollectionRoute 'vlans' -NaturalKey "$($LabID)|$($DesiredVLAN.VLANID)" -DesiredProperty ($VLANProperty) -ExistingObject ($ExistingVLAN) -ManagedState ($ManagedState) -ImmutablePropertyName @('labId')
|
|
|
|
$SyncResultList.Add($SyncResult)
|
|
|
|
Switch ([String]::IsNullOrWhiteSpace($SyncResult.Identifier) -eq $False)
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$VLANIdentifierTable["$($DesiredVLAN.VLANID)"] = "$($SyncResult.Identifier)"
|
|
}
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Phase 03 - Rooms
|
|
$PhaseNumber = 3
|
|
|
|
$Null = $ReportPhaseProgress.InvokeReturnAsIs('Rooms', $DesiredState.RoomList.Count)
|
|
|
|
ForEach ($DesiredRoom In $DesiredState.RoomList)
|
|
{
|
|
$ExistingRoom = $Inventory.RoomList | Where-Object {("$($_.name)" -ieq "$($DesiredRoom.Name)")} | Select-Object -First 1
|
|
|
|
$RoomProperty = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary'
|
|
$RoomProperty.labId = "$($LabID)"
|
|
$RoomProperty.name = "$($DesiredRoom.Name)"
|
|
$RoomProperty.description = "$($DesiredRoom.Description)"
|
|
$RoomProperty.location = "$($DesiredRoom.Location)"
|
|
$RoomProperty.notes = "$($DesiredRoom.Notes)"
|
|
|
|
$SyncResult = Sync-RackpadObject -Session ($Session) -EntityType 'room' -CollectionRoute 'rooms' -NaturalKey "$($LabID)|$($DesiredRoom.Name)" -DesiredProperty ($RoomProperty) -ExistingObject ($ExistingRoom) -ManagedState ($ManagedState) -ImmutablePropertyName @('labId')
|
|
|
|
$SyncResultList.Add($SyncResult)
|
|
|
|
Switch ([String]::IsNullOrWhiteSpace($SyncResult.Identifier) -eq $False)
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$RoomIdentifierTable["$($DesiredRoom.Name)".ToLowerInvariant()] = "$($SyncResult.Identifier)"
|
|
}
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Phase 04 - Racks
|
|
$PhaseNumber = 4
|
|
|
|
$Null = $ReportPhaseProgress.InvokeReturnAsIs('Racks', $DesiredState.RackList.Count)
|
|
|
|
ForEach ($DesiredRack In $DesiredState.RackList)
|
|
{
|
|
$ExistingRack = $Inventory.RackList | Where-Object {("$($_.name)" -ieq "$($DesiredRack.Name)")} | Select-Object -First 1
|
|
|
|
$RackProperty = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary'
|
|
$RackProperty.labId = "$($LabID)"
|
|
$RackProperty.name = "$($DesiredRack.Name)"
|
|
$RackProperty.totalU = [Int]"$($DesiredRack.TotalU)"
|
|
$RackProperty.description = "$($DesiredRack.Description)"
|
|
$RackProperty.location = "$($DesiredRack.Location)"
|
|
$RackProperty.notes = "$($DesiredRack.Notes)"
|
|
$RackProperty.roomId = $Null
|
|
|
|
Switch ($RoomIdentifierTable.ContainsKey("$($DesiredRack.RoomName)".ToLowerInvariant()))
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$RackProperty.roomId = "$($RoomIdentifierTable["$($DesiredRack.RoomName)".ToLowerInvariant()])"
|
|
}
|
|
}
|
|
|
|
$SyncResult = Sync-RackpadObject -Session ($Session) -EntityType 'rack' -CollectionRoute 'racks' -NaturalKey "$($LabID)|$($DesiredRack.Name)" -DesiredProperty ($RackProperty) -ExistingObject ($ExistingRack) -ManagedState ($ManagedState) -ImmutablePropertyName @('labId')
|
|
|
|
$SyncResultList.Add($SyncResult)
|
|
|
|
Switch ([String]::IsNullOrWhiteSpace($SyncResult.Identifier) -eq $False)
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$RackIdentifierTable["$($DesiredRack.Name)".ToLowerInvariant()] = "$($SyncResult.Identifier)"
|
|
}
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Define the rack unit allocator
|
|
# The service rejects a rack placement that carries no start unit, but a broad placement rule cannot name a unit for
|
|
# every device it matches. Anything left unpositioned is therefore assigned the highest free block that fits.
|
|
#
|
|
# Assignment is sticky. A device that already holds a position keeps it, because recomputing a position on every run
|
|
# would move equipment around in the elevation and would report a change forever. Only genuinely unpositioned devices
|
|
# are allocated, and only into space that nothing else claims.
|
|
$RackOccupancyTable = New-Object -TypeName 'System.Collections.Generic.Dictionary[[System.String], [System.Collections.Generic.HashSet[System.Int32]]]'
|
|
$RackHeightTable = New-Object -TypeName 'System.Collections.Generic.Dictionary[[System.String], [System.Int32]]'
|
|
|
|
ForEach ($ExistingRack In $Inventory.RackList)
|
|
{
|
|
$RackHeightTable["$($ExistingRack.id)"] = [Int]"0$($ExistingRack.totalU)"
|
|
}
|
|
|
|
ForEach ($DesiredRack In $DesiredState.RackList)
|
|
{
|
|
Switch ($RackIdentifierTable.ContainsKey("$($DesiredRack.Name)".ToLowerInvariant()))
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$RackHeightTable["$($RackIdentifierTable["$($DesiredRack.Name)".ToLowerInvariant()])"] = [Int]"$($DesiredRack.TotalU)"
|
|
}
|
|
}
|
|
}
|
|
|
|
[ScriptBlock]$ReserveRackUnit = {
|
|
Param
|
|
(
|
|
[String]$RackIdentifier,
|
|
[Int]$StartUnit,
|
|
[Int]$UnitHeight
|
|
)
|
|
|
|
Switch ($RackOccupancyTable.ContainsKey("$($RackIdentifier)"))
|
|
{
|
|
{($_ -eq $False)}
|
|
{
|
|
$RackOccupancyTable["$($RackIdentifier)"] = New-Object -TypeName 'System.Collections.Generic.HashSet[System.Int32]'
|
|
}
|
|
}
|
|
|
|
For ($UnitOffset = 0; $UnitOffset -lt [System.Math]::Max(1, $UnitHeight); $UnitOffset++)
|
|
{
|
|
$Null = $RackOccupancyTable["$($RackIdentifier)"].Add($StartUnit - $UnitOffset)
|
|
}
|
|
}
|
|
|
|
#region Seed occupancy from what the lab already holds, so allocation never lands on top of existing equipment
|
|
ForEach ($ExistingDevice In $Inventory.DeviceList)
|
|
{
|
|
Switch (([String]::IsNullOrWhiteSpace("$($ExistingDevice.rackId)") -eq $False) -and ([Int]"0$($ExistingDevice.startU)" -gt 0))
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$Null = $ReserveRackUnit.InvokeReturnAsIs("$($ExistingDevice.rackId)", [Int]"$($ExistingDevice.startU)", [Int]"0$($ExistingDevice.heightU)")
|
|
}
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Reserve every declared position before anything is allocated
|
|
# A device whose rack unit was named in a placement rule owns that unit regardless of where it happens to fall in the
|
|
# processing order. Reserving the declared positions in a prior pass is what stops an automatically allocated device
|
|
# from taking a unit that a later, pinned device is going to ask for, which the service would reject as an overlap.
|
|
ForEach ($DesiredDevice In $DesiredState.DeviceList)
|
|
{
|
|
$DeclaredUnitIsUsable = ("$($DesiredDevice.Placement)" -ieq 'rack') -and ([String]::IsNullOrWhiteSpace($DesiredDevice.StartU) -eq $False) -and ($RackIdentifierTable.ContainsKey("$($DesiredDevice.RackName)".ToLowerInvariant()) -eq $True)
|
|
|
|
Switch ($DeclaredUnitIsUsable)
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$Null = $ReserveRackUnit.InvokeReturnAsIs("$($RackIdentifierTable["$($DesiredDevice.RackName)".ToLowerInvariant()])", [Int]"$($DesiredDevice.StartU)", [Int]"0$($DesiredDevice.HeightU)")
|
|
}
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
[ScriptBlock]$AllocateRackUnit = {
|
|
Param
|
|
(
|
|
[String]$RackIdentifier,
|
|
[Int]$UnitHeight
|
|
)
|
|
|
|
$AllocatedUnit = 0
|
|
|
|
$RackTotalUnits = 42
|
|
|
|
Switch ($RackHeightTable.ContainsKey("$($RackIdentifier)"))
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$RackTotalUnits = $RackHeightTable["$($RackIdentifier)"]
|
|
}
|
|
}
|
|
|
|
Switch ($RackOccupancyTable.ContainsKey("$($RackIdentifier)"))
|
|
{
|
|
{($_ -eq $False)}
|
|
{
|
|
$RackOccupancyTable["$($RackIdentifier)"] = New-Object -TypeName 'System.Collections.Generic.HashSet[System.Int32]'
|
|
}
|
|
}
|
|
|
|
$EffectiveHeight = [System.Math]::Max(1, $UnitHeight)
|
|
|
|
:CandidateUnitLoop For ($CandidateUnit = $RackTotalUnits; $CandidateUnit -ge $EffectiveHeight; $CandidateUnit--)
|
|
{
|
|
$BlockIsFree = $True
|
|
|
|
For ($UnitOffset = 0; $UnitOffset -lt $EffectiveHeight; $UnitOffset++)
|
|
{
|
|
Switch ($RackOccupancyTable["$($RackIdentifier)"].Contains($CandidateUnit - $UnitOffset))
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$BlockIsFree = $False
|
|
}
|
|
}
|
|
}
|
|
|
|
Switch ($BlockIsFree)
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$AllocatedUnit = $CandidateUnit
|
|
|
|
Break CandidateUnitLoop
|
|
}
|
|
}
|
|
}
|
|
|
|
Write-Output -InputObject ($AllocatedUnit)
|
|
}
|
|
#endregion
|
|
|
|
#region Phases 05 and 06 - Devices are written in two passes so that hosts exist before their guests
|
|
$DevicePassTable = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary'
|
|
$DevicePassTable.'Physical devices' = 5
|
|
$DevicePassTable.'Virtual and attached devices' = 6
|
|
|
|
ForEach ($DevicePassEntry In $DevicePassTable.GetEnumerator())
|
|
{
|
|
$PhaseNumber = [Int]"$($DevicePassEntry.Value)"
|
|
|
|
$DevicePassList = New-Object -TypeName 'System.Collections.Generic.List[PSObject]'
|
|
|
|
ForEach ($DesiredDevice In $DesiredState.DeviceList)
|
|
{
|
|
$DeviceHasParent = [String]::IsNullOrWhiteSpace($DesiredDevice.ParentHostname) -eq $False
|
|
|
|
Switch ((($PhaseNumber -eq 5) -and ($DeviceHasParent -eq $False)) -or (($PhaseNumber -eq 6) -and ($DeviceHasParent -eq $True)))
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$DevicePassList.Add($DesiredDevice)
|
|
}
|
|
}
|
|
}
|
|
|
|
$Null = $ReportPhaseProgress.InvokeReturnAsIs("$($DevicePassEntry.Key)", $DevicePassList.Count)
|
|
|
|
$DevicePassListCount = $DevicePassList.Count
|
|
|
|
For ($DevicePassIndex = 0; $DevicePassIndex -lt $DevicePassListCount; $DevicePassIndex++)
|
|
{
|
|
$DesiredDevice = $DevicePassList[$DevicePassIndex]
|
|
|
|
$ExistingDevice = $Inventory.DeviceList | Where-Object {("$($_.hostname)" -ieq "$($DesiredDevice.Hostname)")} | Select-Object -First 1
|
|
|
|
$DeviceIsRackMounted = "$($DesiredDevice.Placement)" -ieq 'rack'
|
|
|
|
$DeviceProperty = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary'
|
|
$DeviceProperty.labId = "$($LabID)"
|
|
$DeviceProperty.hostname = "$($DesiredDevice.Hostname)"
|
|
$DeviceProperty.displayName = "$($DesiredDevice.DisplayName)"
|
|
$DeviceProperty.deviceType = "$($DesiredDevice.DeviceType)"
|
|
$DeviceProperty.manufacturer = "$($DesiredDevice.Manufacturer)"
|
|
$DeviceProperty.model = "$($DesiredDevice.Model)"
|
|
$DeviceProperty.serial = "$($DesiredDevice.SerialNumber)"
|
|
$DeviceProperty.managementIp = "$($DesiredDevice.ManagementIP)"
|
|
$DeviceProperty.macAddress = "$($DesiredDevice.MACAddress)"
|
|
$DeviceProperty.status = "$($DesiredDevice.Status)"
|
|
$DeviceProperty.placement = "$($DesiredDevice.Placement)"
|
|
$DeviceProperty.networkMode = "$($DesiredDevice.NetworkMode)"
|
|
$DeviceProperty.specs = "$($DesiredDevice.Specifications)"
|
|
$DeviceProperty.notes = "$($DesiredDevice.Notes)"
|
|
$DeviceProperty.tags = $DesiredDevice.TagList.ToArray()
|
|
$DeviceProperty.cpuCores = $DesiredDevice.CPUCoreCount
|
|
$DeviceProperty.memoryGb = $DesiredDevice.MemoryGB
|
|
$DeviceProperty.storageGb = $DesiredDevice.StorageGB
|
|
$DeviceProperty.rackId = $Null
|
|
$DeviceProperty.roomId = $Null
|
|
$DeviceProperty.parentDeviceId = $Null
|
|
|
|
#region Rack geometry keys are omitted entirely unless the device is rack mounted
|
|
# The service supplies its own defaults for startU, heightU, face, and rackSlot, so transmitting a
|
|
# null would be answered with that default and every subsequent run would report a difference that
|
|
# can never be satisfied. Omitting the keys is what the user interface itself does.
|
|
Switch ($DeviceIsRackMounted)
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$DeviceProperty.startU = $Null
|
|
$DeviceProperty.heightU = [Int]"0$($DesiredDevice.HeightU)"
|
|
$DeviceProperty.face = "$($DesiredDevice.Face)"
|
|
$DeviceProperty.rackSlot = "$($DesiredDevice.RackSlot)"
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
Switch ($DeviceIsRackMounted)
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
Switch ($RackIdentifierTable.ContainsKey("$($DesiredDevice.RackName)".ToLowerInvariant()))
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$DeviceProperty.rackId = "$($RackIdentifierTable["$($DesiredDevice.RackName)".ToLowerInvariant()])"
|
|
}
|
|
}
|
|
|
|
#region Resolve the rack unit, preferring what was declared, then what the device already holds, then a fresh allocation
|
|
$ResolvedStartUnit = 0
|
|
|
|
Switch ([String]::IsNullOrWhiteSpace($DesiredDevice.StartU) -eq $False)
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$ResolvedStartUnit = [Int]"$($DesiredDevice.StartU)"
|
|
}
|
|
}
|
|
|
|
Switch (($ResolvedStartUnit -le 0) -and ([Int]"0$($ExistingDevice.startU)" -gt 0) -and ("$($ExistingDevice.rackId)" -ieq "$($DeviceProperty.rackId)"))
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$ResolvedStartUnit = [Int]"$($ExistingDevice.startU)"
|
|
}
|
|
}
|
|
|
|
Switch (($ResolvedStartUnit -le 0) -and ([String]::IsNullOrWhiteSpace($DeviceProperty.rackId) -eq $False))
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$ResolvedStartUnit = [Int]$AllocateRackUnit.InvokeReturnAsIs("$($DeviceProperty.rackId)", [Int]"0$($DesiredDevice.HeightU)")
|
|
|
|
Switch ($ResolvedStartUnit -gt 0)
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
Write-Verbose -Message "[$([System.DateTime]::UtcNow.ToString('yyyy/MM/dd HH:mm:ss.FFF'))] - [INFO] - `"$($DesiredDevice.Hostname)`" was assigned rack unit $($ResolvedStartUnit), which was the highest free block that fits."
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#region A rack with no room left falls back to loose room placement rather than failing the write
|
|
Switch ($ResolvedStartUnit -gt 0)
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$DeviceProperty.startU = $ResolvedStartUnit
|
|
|
|
$Null = $ReserveRackUnit.InvokeReturnAsIs("$($DeviceProperty.rackId)", $ResolvedStartUnit, [Int]"0$($DesiredDevice.HeightU)")
|
|
}
|
|
|
|
{($_ -eq $False)}
|
|
{
|
|
Write-Warning -Message "[$([System.DateTime]::UtcNow.ToString('yyyy/MM/dd HH:mm:ss.FFF'))] - [WARNING] - `"$($DesiredDevice.Hostname)`" could not be given a rack unit in `"$($DesiredDevice.RackName)`" and will be placed loose in the room instead. [Reason: The rack has no free block of $([System.Math]::Max(1, [Int]"0$($DesiredDevice.HeightU)")) unit(s).]"
|
|
|
|
$DeviceProperty.placement = 'room'
|
|
$DeviceProperty.rackId = $Null
|
|
$DeviceProperty.Remove('startU')
|
|
$DeviceProperty.Remove('heightU')
|
|
$DeviceProperty.Remove('face')
|
|
$DeviceProperty.Remove('rackSlot')
|
|
}
|
|
}
|
|
#endregion
|
|
#endregion
|
|
}
|
|
|
|
{($_ -eq $False)}
|
|
{
|
|
Switch ($RoomIdentifierTable.ContainsKey("$($DesiredDevice.RoomName)".ToLowerInvariant()))
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$DeviceProperty.roomId = "$($RoomIdentifierTable["$($DesiredDevice.RoomName)".ToLowerInvariant()])"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Switch ($DeviceIdentifierTable.ContainsKey("$($DesiredDevice.ParentHostname)".ToLowerInvariant()))
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$DeviceProperty.parentDeviceId = "$($DeviceIdentifierTable["$($DesiredDevice.ParentHostname)".ToLowerInvariant()])"
|
|
}
|
|
}
|
|
|
|
$SyncResult = Sync-RackpadObject -Session ($Session) -EntityType 'device' -CollectionRoute 'devices' -NaturalKey "$($LabID)|$($DesiredDevice.Hostname)" -DesiredProperty ($DeviceProperty) -ExistingObject ($ExistingDevice) -ManagedState ($ManagedState) -ImmutablePropertyName @('labId')
|
|
|
|
$SyncResultList.Add($SyncResult)
|
|
|
|
Switch ([String]::IsNullOrWhiteSpace($SyncResult.Identifier) -eq $False)
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$DeviceIdentifierTable["$($DesiredDevice.Hostname)".ToLowerInvariant()] = "$($SyncResult.Identifier)"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Phase 07 - Virtual switches
|
|
$PhaseNumber = 7
|
|
|
|
$Null = $ReportPhaseProgress.InvokeReturnAsIs('Virtual switches', $DesiredState.VirtualSwitchList.Count)
|
|
|
|
ForEach ($DesiredVirtualSwitch In $DesiredState.VirtualSwitchList)
|
|
{
|
|
$HostDeviceIdentifier = ''
|
|
|
|
Switch ($DeviceIdentifierTable.ContainsKey("$($DesiredVirtualSwitch.HostHostname)".ToLowerInvariant()))
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$HostDeviceIdentifier = "$($DeviceIdentifierTable["$($DesiredVirtualSwitch.HostHostname)".ToLowerInvariant()])"
|
|
}
|
|
}
|
|
|
|
Switch ([String]::IsNullOrWhiteSpace($HostDeviceIdentifier) -eq $False)
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$ExistingVirtualSwitch = $Inventory.VirtualSwitchList | Where-Object {("$($_.hostDeviceId)" -ieq "$($HostDeviceIdentifier)") -and ("$($_.name)" -ieq "$($DesiredVirtualSwitch.Name)")} | Select-Object -First 1
|
|
|
|
$VirtualSwitchProperty = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary'
|
|
$VirtualSwitchProperty.hostDeviceId = "$($HostDeviceIdentifier)"
|
|
$VirtualSwitchProperty.name = "$($DesiredVirtualSwitch.Name)"
|
|
$VirtualSwitchProperty.kind = "$($DesiredVirtualSwitch.Kind)"
|
|
$VirtualSwitchProperty.notes = "$($DesiredVirtualSwitch.Notes)"
|
|
|
|
$SyncResult = Sync-RackpadObject -Session ($Session) -EntityType 'virtual-switch' -CollectionRoute 'virtual-switches' -NaturalKey "$($DesiredVirtualSwitch.HostHostname)|$($DesiredVirtualSwitch.Name)" -DesiredProperty ($VirtualSwitchProperty) -ExistingObject ($ExistingVirtualSwitch) -ManagedState ($ManagedState) -ImmutablePropertyName @('hostDeviceId')
|
|
|
|
$SyncResultList.Add($SyncResult)
|
|
|
|
Switch ([String]::IsNullOrWhiteSpace($SyncResult.Identifier) -eq $False)
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$VirtualSwitchIdentifierTable["$($DesiredVirtualSwitch.HostHostname)|$($DesiredVirtualSwitch.Name)".ToLowerInvariant()] = "$($SyncResult.Identifier)"
|
|
}
|
|
}
|
|
}
|
|
|
|
{($_ -eq $False)}
|
|
{
|
|
Write-Warning -Message "[$([System.DateTime]::UtcNow.ToString('yyyy/MM/dd HH:mm:ss.FFF'))] - [WARNING] - Skipping virtual switch `"$($DesiredVirtualSwitch.Name)`". [Reason: The host device `"$($DesiredVirtualSwitch.HostHostname)`" could not be resolved.]"
|
|
}
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Phase 08 - Ports
|
|
$PhaseNumber = 8
|
|
|
|
$Null = $ReportPhaseProgress.InvokeReturnAsIs('Ports', $DesiredState.PortList.Count)
|
|
|
|
$DesiredPortListCount = $DesiredState.PortList.Count
|
|
|
|
For ($DesiredPortIndex = 0; $DesiredPortIndex -lt $DesiredPortListCount; $DesiredPortIndex++)
|
|
{
|
|
$DesiredPort = $DesiredState.PortList[$DesiredPortIndex]
|
|
|
|
$PortDeviceIdentifier = ''
|
|
|
|
Switch ($DeviceIdentifierTable.ContainsKey("$($DesiredPort.DeviceHostname)".ToLowerInvariant()))
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$PortDeviceIdentifier = "$($DeviceIdentifierTable["$($DesiredPort.DeviceHostname)".ToLowerInvariant()])"
|
|
}
|
|
}
|
|
|
|
Switch ([String]::IsNullOrWhiteSpace($PortDeviceIdentifier) -eq $False)
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$ExistingPort = $Inventory.PortList | Where-Object {("$($_.deviceId)" -ieq "$($PortDeviceIdentifier)") -and ("$($_.name)" -ieq "$($DesiredPort.Name)")} | Select-Object -First 1
|
|
|
|
$PortProperty = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary'
|
|
$PortProperty.deviceId = "$($PortDeviceIdentifier)"
|
|
$PortProperty.name = "$($DesiredPort.Name)"
|
|
$PortProperty.position = [Int]"0$($DesiredPort.Position)"
|
|
$PortProperty.kind = "$($DesiredPort.Kind)"
|
|
$PortProperty.speed = "$($DesiredPort.Speed)"
|
|
$PortProperty.mode = "$($DesiredPort.Mode)"
|
|
$PortProperty.linkState = "$($DesiredPort.LinkState)"
|
|
$PortProperty.face = "$($DesiredPort.Face)"
|
|
$PortProperty.description = "$($DesiredPort.Description)"
|
|
$PortProperty.macAddress = "$($DesiredPort.MACAddress)"
|
|
$PortProperty.vlanId = $Null
|
|
$PortProperty.virtualSwitchId = $Null
|
|
|
|
Switch ($VLANIdentifierTable.ContainsKey("$($DesiredPort.VLANID)"))
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$PortProperty.vlanId = "$($VLANIdentifierTable["$($DesiredPort.VLANID)"])"
|
|
}
|
|
}
|
|
|
|
Switch ($VirtualSwitchIdentifierTable.ContainsKey("$($DesiredPort.VirtualSwitchHostHostname)|$($DesiredPort.VirtualSwitchName)".ToLowerInvariant()))
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$PortProperty.virtualSwitchId = "$($VirtualSwitchIdentifierTable["$($DesiredPort.VirtualSwitchHostHostname)|$($DesiredPort.VirtualSwitchName)".ToLowerInvariant()])"
|
|
}
|
|
}
|
|
|
|
$SyncResult = Sync-RackpadObject -Session ($Session) -EntityType 'port' -CollectionRoute 'ports' -NaturalKey "$($DesiredPort.DeviceHostname)|$($DesiredPort.Name)" -DesiredProperty ($PortProperty) -ExistingObject ($ExistingPort) -ManagedState ($ManagedState) -ImmutablePropertyName @('deviceId')
|
|
|
|
$SyncResultList.Add($SyncResult)
|
|
|
|
Switch ([String]::IsNullOrWhiteSpace($SyncResult.Identifier) -eq $False)
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$PortIdentifierTable["$($DesiredPort.DeviceHostname)|$($DesiredPort.Name)".ToLowerInvariant()] = "$($SyncResult.Identifier)"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Phase 09 - Subnets
|
|
$PhaseNumber = 9
|
|
|
|
$Null = $ReportPhaseProgress.InvokeReturnAsIs('Subnets', $DesiredState.SubnetList.Count)
|
|
|
|
ForEach ($DesiredSubnet In $DesiredState.SubnetList)
|
|
{
|
|
$ExistingSubnet = $Inventory.SubnetList | Where-Object {("$($_.cidr)" -ieq "$($DesiredSubnet.CIDR)")} | Select-Object -First 1
|
|
|
|
$SubnetProperty = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary'
|
|
$SubnetProperty.labId = "$($LabID)"
|
|
$SubnetProperty.cidr = "$($DesiredSubnet.CIDR)"
|
|
$SubnetProperty.name = "$($DesiredSubnet.Name)"
|
|
$SubnetProperty.description = "$($DesiredSubnet.Description)"
|
|
$SubnetProperty.gateway = "$($DesiredSubnet.Gateway)"
|
|
$SubnetProperty.dnsServers = $DesiredSubnet.DNSServerList.ToArray()
|
|
$SubnetProperty.vlanId = $Null
|
|
|
|
Switch ($VLANIdentifierTable.ContainsKey("$($DesiredSubnet.VLANID)"))
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$SubnetProperty.vlanId = "$($VLANIdentifierTable["$($DesiredSubnet.VLANID)"])"
|
|
}
|
|
}
|
|
|
|
$SyncResult = Sync-RackpadObject -Session ($Session) -EntityType 'subnet' -CollectionRoute 'subnets' -NaturalKey "$($LabID)|$($DesiredSubnet.CIDR)" -DesiredProperty ($SubnetProperty) -ExistingObject ($ExistingSubnet) -ManagedState ($ManagedState) -ImmutablePropertyName @('labId')
|
|
|
|
$SyncResultList.Add($SyncResult)
|
|
|
|
Switch ([String]::IsNullOrWhiteSpace($SyncResult.Identifier) -eq $False)
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$SubnetIdentifierTable["$($DesiredSubnet.CIDR)".ToLowerInvariant()] = "$($SyncResult.Identifier)"
|
|
}
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Phase 10 - IP zones, which are the address ranges that serve each room and rack
|
|
$PhaseNumber = 10
|
|
|
|
$Null = $ReportPhaseProgress.InvokeReturnAsIs('IP zones', $DesiredState.IPZoneList.Count)
|
|
|
|
ForEach ($DesiredIPZone In $DesiredState.IPZoneList)
|
|
{
|
|
Switch ($SubnetIdentifierTable.ContainsKey("$($DesiredIPZone.SubnetCIDR)".ToLowerInvariant()))
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$ZoneSubnetIdentifier = "$($SubnetIdentifierTable["$($DesiredIPZone.SubnetCIDR)".ToLowerInvariant()])"
|
|
|
|
$ExistingIPZone = $Inventory.IPZoneList | Where-Object {("$($_.subnetId)" -ieq "$($ZoneSubnetIdentifier)") -and ("$($_.startIp)" -ieq "$($DesiredIPZone.StartIP)") -and ("$($_.endIp)" -ieq "$($DesiredIPZone.EndIP)")} | Select-Object -First 1
|
|
|
|
$IPZoneProperty = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary'
|
|
$IPZoneProperty.subnetId = "$($ZoneSubnetIdentifier)"
|
|
$IPZoneProperty.kind = "$($DesiredIPZone.Kind)"
|
|
$IPZoneProperty.startIp = "$($DesiredIPZone.StartIP)"
|
|
$IPZoneProperty.endIp = "$($DesiredIPZone.EndIP)"
|
|
$IPZoneProperty.description = "$($DesiredIPZone.Description)"
|
|
|
|
$SyncResult = Sync-RackpadObject -Session ($Session) -EntityType 'ip-zone' -CollectionRoute 'ip-zones' -NaturalKey "$($DesiredIPZone.SubnetCIDR)|$($DesiredIPZone.StartIP)|$($DesiredIPZone.EndIP)" -DesiredProperty ($IPZoneProperty) -ExistingObject ($ExistingIPZone) -ManagedState ($ManagedState) -ImmutablePropertyName @('subnetId')
|
|
|
|
$SyncResultList.Add($SyncResult)
|
|
}
|
|
|
|
{($_ -eq $False)}
|
|
{
|
|
Write-Warning -Message "[$([System.DateTime]::UtcNow.ToString('yyyy/MM/dd HH:mm:ss.FFF'))] - [WARNING] - Skipping the IP range $($DesiredIPZone.StartIP) to $($DesiredIPZone.EndIP). [Reason: The subnet `"$($DesiredIPZone.SubnetCIDR)`" was not discovered by any source.]"
|
|
}
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Phase 11 - IP assignments
|
|
$PhaseNumber = 11
|
|
|
|
$Null = $ReportPhaseProgress.InvokeReturnAsIs('IP assignments', $DesiredState.IPAssignmentList.Count)
|
|
|
|
$ObservedIPAssignmentKeySet = New-Object -TypeName 'System.Collections.Generic.HashSet[System.String]'
|
|
|
|
ForEach ($DesiredIPAssignment In $DesiredState.IPAssignmentList)
|
|
{
|
|
#region Resolve the subnet that contains this address
|
|
$ResolvedSubnetCIDR = ''
|
|
|
|
ForEach ($DesiredSubnet In $DesiredState.SubnetList)
|
|
{
|
|
Switch (($TestIPAddressWithinCIDR.InvokeReturnAsIs("$($DesiredIPAssignment.IPAddress)", "$($DesiredSubnet.CIDR)") -eq $True) -and ([String]::IsNullOrWhiteSpace($ResolvedSubnetCIDR) -eq $True))
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$ResolvedSubnetCIDR = "$($DesiredSubnet.CIDR)"
|
|
}
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
$AssignmentIsResolvable = ($SubnetIdentifierTable.ContainsKey("$($ResolvedSubnetCIDR)".ToLowerInvariant()) -eq $True) -and ($DeviceIdentifierTable.ContainsKey("$($DesiredIPAssignment.DeviceHostname)".ToLowerInvariant()) -eq $True)
|
|
|
|
$AssignmentNaturalKey = "$($ResolvedSubnetCIDR)|$($DesiredIPAssignment.IPAddress)".ToLowerInvariant()
|
|
|
|
Switch (($AssignmentIsResolvable -eq $True) -and ($ObservedIPAssignmentKeySet.Add($AssignmentNaturalKey) -eq $True))
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$SubnetIdentifier = "$($SubnetIdentifierTable["$($ResolvedSubnetCIDR)".ToLowerInvariant()])"
|
|
|
|
$ExistingIPAssignment = $Inventory.IPAssignmentList | Where-Object {("$($_.subnetId)" -ieq "$($SubnetIdentifier)") -and ("$($_.ipAddress)" -ieq "$($DesiredIPAssignment.IPAddress)")} | Select-Object -First 1
|
|
|
|
#region The address that a subnet uses as its gateway cannot be documented as an ordinary endpoint
|
|
# The service rejects that combination and asks for an interface, reserved, or infrastructure
|
|
# classification instead, which is reasonable: a routed edge holding its own gateway address is
|
|
# infrastructure rather than a host that happens to sit on the network.
|
|
$ResolvedAssignmentType = "$($DesiredIPAssignment.AssignmentType)"
|
|
|
|
ForEach ($DesiredSubnet In $DesiredState.SubnetList)
|
|
{
|
|
Switch (("$($DesiredSubnet.CIDR)" -ieq "$($ResolvedSubnetCIDR)") -and ("$($DesiredSubnet.Gateway)" -ieq "$($DesiredIPAssignment.IPAddress)") -and ([String]::IsNullOrWhiteSpace($DesiredSubnet.Gateway) -eq $False))
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$ResolvedAssignmentType = 'infrastructure'
|
|
}
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
$IPAssignmentProperty = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary'
|
|
$IPAssignmentProperty.subnetId = "$($SubnetIdentifier)"
|
|
$IPAssignmentProperty.ipAddress = "$($DesiredIPAssignment.IPAddress)"
|
|
$IPAssignmentProperty.assignmentType = "$($ResolvedAssignmentType)"
|
|
$IPAssignmentProperty.allocationMode = "$($DesiredIPAssignment.AllocationMode)"
|
|
$IPAssignmentProperty.deviceId = "$($DeviceIdentifierTable["$($DesiredIPAssignment.DeviceHostname)".ToLowerInvariant()])"
|
|
$IPAssignmentProperty.hostname = "$($DesiredIPAssignment.Hostname)"
|
|
$IPAssignmentProperty.description = "$($DesiredIPAssignment.Description)"
|
|
$IPAssignmentProperty.dhcpScopeId = $Null
|
|
|
|
$SyncResult = Sync-RackpadObject -Session ($Session) -EntityType 'ip-assignment' -CollectionRoute 'ip-assignments' -NaturalKey "$($AssignmentNaturalKey)" -DesiredProperty ($IPAssignmentProperty) -ExistingObject ($ExistingIPAssignment) -ManagedState ($ManagedState) -ImmutablePropertyName @('subnetId')
|
|
|
|
$SyncResultList.Add($SyncResult)
|
|
}
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Phase 12 - Cable links
|
|
$PhaseNumber = 12
|
|
|
|
$Null = $ReportPhaseProgress.InvokeReturnAsIs('Cable links', $DesiredState.PortLinkList.Count)
|
|
|
|
$ObservedPortLinkKeySet = New-Object -TypeName 'System.Collections.Generic.HashSet[System.String]'
|
|
|
|
ForEach ($DesiredPortLink In $DesiredState.PortLinkList)
|
|
{
|
|
$FromPortKey = "$($DesiredPortLink.FromDeviceHostname)|$($DesiredPortLink.FromPortName)".ToLowerInvariant()
|
|
$ToPortKey = "$($DesiredPortLink.ToDeviceHostname)|$($DesiredPortLink.ToPortName)".ToLowerInvariant()
|
|
|
|
$LinkIsResolvable = ($PortIdentifierTable.ContainsKey($FromPortKey) -eq $True) -and ($PortIdentifierTable.ContainsKey($ToPortKey) -eq $True)
|
|
|
|
$LinkNaturalKey = (@("$($FromPortKey)", "$($ToPortKey)") | Sort-Object) -Join '<->'
|
|
|
|
Switch (($LinkIsResolvable -eq $True) -and ($ObservedPortLinkKeySet.Add($LinkNaturalKey) -eq $True))
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$FromPortIdentifier = "$($PortIdentifierTable[$FromPortKey])"
|
|
$ToPortIdentifier = "$($PortIdentifierTable[$ToPortKey])"
|
|
|
|
$ExistingPortLink = $Inventory.PortLinkList | Where-Object {(("$($_.fromPortId)" -ieq "$($FromPortIdentifier)") -and ("$($_.toPortId)" -ieq "$($ToPortIdentifier)")) -or (("$($_.fromPortId)" -ieq "$($ToPortIdentifier)") -and ("$($_.toPortId)" -ieq "$($FromPortIdentifier)"))} | Select-Object -First 1
|
|
|
|
$PortLinkProperty = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary'
|
|
$PortLinkProperty.fromPortId = "$($FromPortIdentifier)"
|
|
$PortLinkProperty.toPortId = "$($ToPortIdentifier)"
|
|
$PortLinkProperty.cableType = "$($DesiredPortLink.CableType)"
|
|
$PortLinkProperty.cableLength = "$($DesiredPortLink.CableLength)"
|
|
$PortLinkProperty.color = "$($DesiredPortLink.Color)"
|
|
$PortLinkProperty.notes = "$($DesiredPortLink.Notes)"
|
|
|
|
$SyncResult = Sync-RackpadObject -Session ($Session) -EntityType 'port-link' -CollectionRoute 'port-links' -NaturalKey "$($LinkNaturalKey)" -DesiredProperty ($PortLinkProperty) -ExistingObject ($ExistingPortLink) -ManagedState ($ManagedState) -ImmutablePropertyName @('fromPortId', 'toPortId')
|
|
|
|
$SyncResultList.Add($SyncResult)
|
|
}
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Phase 13 - Wireless
|
|
$PhaseNumber = 13
|
|
|
|
Switch ($Options.ManageWireless)
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$Null = $ReportPhaseProgress.InvokeReturnAsIs('Wireless', ($DesiredState.WirelessControllerList.Count + $DesiredState.WirelessSSIDList.Count + $DesiredState.WirelessAccessPointList.Count + $DesiredState.WirelessRadioList.Count + $DesiredState.WirelessAssociationList.Count))
|
|
|
|
#region Controllers
|
|
ForEach ($DesiredController In $DesiredState.WirelessControllerList)
|
|
{
|
|
$ExistingController = $Inventory.WirelessControllerList | Where-Object {("$($_.name)" -ieq "$($DesiredController.Name)")} | Select-Object -First 1
|
|
|
|
$ControllerProperty = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary'
|
|
$ControllerProperty.labId = "$($LabID)"
|
|
$ControllerProperty.name = "$($DesiredController.Name)"
|
|
$ControllerProperty.vendor = "$($DesiredController.Vendor)"
|
|
$ControllerProperty.model = "$($DesiredController.Model)"
|
|
$ControllerProperty.managementIp = "$($DesiredController.ManagementIP)"
|
|
$ControllerProperty.notes = "$($DesiredController.Notes)"
|
|
$ControllerProperty.deviceId = $Null
|
|
|
|
Switch ($DeviceIdentifierTable.ContainsKey("$($DesiredController.DeviceHostname)".ToLowerInvariant()))
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$ControllerProperty.deviceId = "$($DeviceIdentifierTable["$($DesiredController.DeviceHostname)".ToLowerInvariant()])"
|
|
}
|
|
}
|
|
|
|
$SyncResult = Sync-RackpadObject -Session ($Session) -EntityType 'wifi-controller' -CollectionRoute 'wifi/controllers' -NaturalKey "$($LabID)|$($DesiredController.Name)" -DesiredProperty ($ControllerProperty) -ExistingObject ($ExistingController) -ManagedState ($ManagedState) -ImmutablePropertyName @('labId')
|
|
|
|
$SyncResultList.Add($SyncResult)
|
|
|
|
Switch ([String]::IsNullOrWhiteSpace($SyncResult.Identifier) -eq $False)
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$WirelessControllerIdentifierTable["$($DesiredController.Name)".ToLowerInvariant()] = "$($SyncResult.Identifier)"
|
|
}
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region SSIDs
|
|
ForEach ($DesiredSSID In $DesiredState.WirelessSSIDList)
|
|
{
|
|
$ExistingSSID = $Inventory.WirelessSSIDList | Where-Object {("$($_.name)" -ieq "$($DesiredSSID.Name)")} | Select-Object -First 1
|
|
|
|
$SSIDProperty = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary'
|
|
$SSIDProperty.labId = "$($LabID)"
|
|
$SSIDProperty.name = "$($DesiredSSID.Name)"
|
|
$SSIDProperty.purpose = "$($DesiredSSID.Purpose)"
|
|
$SSIDProperty.security = "$($DesiredSSID.Security)"
|
|
$SSIDProperty.hidden = [Boolean]$DesiredSSID.Hidden
|
|
$SSIDProperty.color = "$($DesiredSSID.Color)"
|
|
$SSIDProperty.vlanId = $Null
|
|
|
|
Switch ($VLANIdentifierTable.ContainsKey("$($DesiredSSID.VLANID)"))
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$SSIDProperty.vlanId = "$($VLANIdentifierTable["$($DesiredSSID.VLANID)"])"
|
|
}
|
|
}
|
|
|
|
$SyncResult = Sync-RackpadObject -Session ($Session) -EntityType 'wifi-ssid' -CollectionRoute 'wifi/ssids' -NaturalKey "$($LabID)|$($DesiredSSID.Name)" -DesiredProperty ($SSIDProperty) -ExistingObject ($ExistingSSID) -ManagedState ($ManagedState) -ImmutablePropertyName @('labId')
|
|
|
|
$SyncResultList.Add($SyncResult)
|
|
|
|
Switch ([String]::IsNullOrWhiteSpace($SyncResult.Identifier) -eq $False)
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$SSIDIdentifierTable["$($DesiredSSID.Name)".ToLowerInvariant()] = "$($SyncResult.Identifier)"
|
|
}
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Access points are addressed by device identifier and are always written using PUT
|
|
ForEach ($DesiredAccessPoint In $DesiredState.WirelessAccessPointList)
|
|
{
|
|
Switch ($DeviceIdentifierTable.ContainsKey("$($DesiredAccessPoint.DeviceHostname)".ToLowerInvariant()))
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$AccessPointDeviceIdentifier = "$($DeviceIdentifierTable["$($DesiredAccessPoint.DeviceHostname)".ToLowerInvariant()])"
|
|
|
|
$ExistingAccessPoint = $Inventory.WirelessAccessPointList | Where-Object {("$($_.deviceId)" -ieq "$($AccessPointDeviceIdentifier)")} | Select-Object -First 1
|
|
|
|
$AccessPointProperty = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary'
|
|
$AccessPointProperty.controllerId = $Null
|
|
$AccessPointProperty.location = "$($DesiredAccessPoint.Location)"
|
|
$AccessPointProperty.firmwareVersion = "$($DesiredAccessPoint.FirmwareVersion)"
|
|
$AccessPointProperty.notes = "$($DesiredAccessPoint.Notes)"
|
|
|
|
Switch ($WirelessControllerIdentifierTable.ContainsKey("$($DesiredAccessPoint.ControllerName)".ToLowerInvariant()))
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$AccessPointProperty.controllerId = "$($WirelessControllerIdentifierTable["$($DesiredAccessPoint.ControllerName)".ToLowerInvariant()])"
|
|
}
|
|
}
|
|
|
|
$SyncResult = Sync-RackpadObject -Session ($Session) -EntityType 'wifi-access-point' -CollectionRoute 'wifi/access-points' -NaturalKey "$($DesiredAccessPoint.DeviceHostname)" -DesiredProperty ($AccessPointProperty) -ExistingObject ($ExistingAccessPoint) -ManagedState ($ManagedState) -UpdateMethod 'PUT' -UpdateRoute "wifi/access-points/$($AccessPointDeviceIdentifier)"
|
|
|
|
$SyncResultList.Add($SyncResult)
|
|
}
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Radios
|
|
ForEach ($DesiredRadio In $DesiredState.WirelessRadioList)
|
|
{
|
|
Switch ($DeviceIdentifierTable.ContainsKey("$($DesiredRadio.AccessPointHostname)".ToLowerInvariant()))
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$RadioDeviceIdentifier = "$($DeviceIdentifierTable["$($DesiredRadio.AccessPointHostname)".ToLowerInvariant()])"
|
|
|
|
$ExistingRadio = $Inventory.WirelessRadioList | Where-Object {("$($_.apDeviceId)" -ieq "$($RadioDeviceIdentifier)") -and ("$($_.slotName)" -ieq "$($DesiredRadio.SlotName)")} | Select-Object -First 1
|
|
|
|
$RadioSSIDIdentifierList = New-Object -TypeName 'System.Collections.Generic.List[String]'
|
|
|
|
ForEach ($RadioSSIDName In $DesiredRadio.SSIDNameList)
|
|
{
|
|
Switch ($SSIDIdentifierTable.ContainsKey("$($RadioSSIDName)".ToLowerInvariant()))
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$RadioSSIDIdentifierList.Add("$($SSIDIdentifierTable["$($RadioSSIDName)".ToLowerInvariant()])")
|
|
}
|
|
}
|
|
}
|
|
|
|
$RadioProperty = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary'
|
|
$RadioProperty.apDeviceId = "$($RadioDeviceIdentifier)"
|
|
$RadioProperty.slotName = "$($DesiredRadio.SlotName)"
|
|
$RadioProperty.band = "$($DesiredRadio.Band)"
|
|
$RadioProperty.channel = "$($DesiredRadio.Channel)"
|
|
$RadioProperty.channelWidth = "$($DesiredRadio.ChannelWidth)"
|
|
$RadioProperty.txPower = "$($DesiredRadio.TransmitPower)"
|
|
$RadioProperty.ssidIds = $RadioSSIDIdentifierList.ToArray()
|
|
$RadioProperty.notes = "$($DesiredRadio.Notes)"
|
|
|
|
$SyncResult = Sync-RackpadObject -Session ($Session) -EntityType 'wifi-radio' -CollectionRoute 'wifi/radios' -NaturalKey "$($DesiredRadio.AccessPointHostname)|$($DesiredRadio.SlotName)" -DesiredProperty ($RadioProperty) -ExistingObject ($ExistingRadio) -ManagedState ($ManagedState) -ImmutablePropertyName @('apDeviceId')
|
|
|
|
$SyncResultList.Add($SyncResult)
|
|
|
|
Switch ([String]::IsNullOrWhiteSpace($SyncResult.Identifier) -eq $False)
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$WirelessRadioIdentifierTable["$($DesiredRadio.AccessPointHostname)|$($DesiredRadio.SlotName)".ToLowerInvariant()] = "$($SyncResult.Identifier)"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Client associations are addressed by the client device identifier, which is also carried within the payload
|
|
ForEach ($DesiredAssociation In $DesiredState.WirelessAssociationList)
|
|
{
|
|
$ClientDeviceKey = "$($DesiredAssociation.ClientHostname)".ToLowerInvariant()
|
|
$AccessPointKey = "$($DesiredAssociation.AccessPointHostname)".ToLowerInvariant()
|
|
|
|
$AssociationIsResolvable = ($DeviceIdentifierTable.ContainsKey($AccessPointKey) -eq $True) -and ($DeviceIdentifierTable.ContainsKey($ClientDeviceKey) -eq $True)
|
|
|
|
Switch ($AssociationIsResolvable)
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$ClientDeviceIdentifier = "$($DeviceIdentifierTable[$ClientDeviceKey])"
|
|
|
|
$ExistingAssociation = $Inventory.WirelessAssociationList | Where-Object {("$($_.clientDeviceId)" -ieq "$($ClientDeviceIdentifier)")} | Select-Object -First 1
|
|
|
|
$AssociationProperty = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary'
|
|
$AssociationProperty.clientDeviceId = "$($ClientDeviceIdentifier)"
|
|
$AssociationProperty.apDeviceId = "$($DeviceIdentifierTable[$AccessPointKey])"
|
|
$AssociationProperty.radioId = $Null
|
|
$AssociationProperty.ssidId = $Null
|
|
$AssociationProperty.band = "$($DesiredAssociation.Band)"
|
|
$AssociationProperty.channel = "$($DesiredAssociation.Channel)"
|
|
$AssociationProperty.signalDbm = $DesiredAssociation.SignalDbm
|
|
$AssociationProperty.lastSeen = $Null
|
|
$AssociationProperty.lastRoamAt = $Null
|
|
$AssociationProperty.notes = "$($DesiredAssociation.Notes)"
|
|
|
|
Switch ($WirelessRadioIdentifierTable.ContainsKey("$($DesiredAssociation.AccessPointHostname)|$($DesiredAssociation.RadioSlotName)".ToLowerInvariant()))
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$AssociationProperty.radioId = "$($WirelessRadioIdentifierTable["$($DesiredAssociation.AccessPointHostname)|$($DesiredAssociation.RadioSlotName)".ToLowerInvariant()])"
|
|
}
|
|
}
|
|
|
|
Switch ($SSIDIdentifierTable.ContainsKey("$($DesiredAssociation.SSIDName)".ToLowerInvariant()))
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$AssociationProperty.ssidId = "$($SSIDIdentifierTable["$($DesiredAssociation.SSIDName)".ToLowerInvariant()])"
|
|
}
|
|
}
|
|
|
|
Switch ($Null -ine $DesiredAssociation.LastSeen)
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$AssociationProperty.lastSeen = $DesiredAssociation.LastSeen.ToString('o')
|
|
}
|
|
}
|
|
|
|
$SyncResult = Sync-RackpadObject -Session ($Session) -EntityType 'wifi-association' -CollectionRoute 'wifi/associations' -NaturalKey "$($DesiredAssociation.ClientHostname)" -DesiredProperty ($AssociationProperty) -ExistingObject ($ExistingAssociation) -ManagedState ($ManagedState) -UpdateMethod 'PUT' -UpdateRoute "wifi/associations/$($ClientDeviceIdentifier)"
|
|
|
|
$SyncResultList.Add($SyncResult)
|
|
}
|
|
}
|
|
}
|
|
#endregion
|
|
}
|
|
|
|
{($_ -eq $False)}
|
|
{
|
|
Write-Verbose -Message "[$([System.DateTime]::UtcNow.ToString('yyyy/MM/dd HH:mm:ss.FFF'))] - [INFO] - Phase $($PhaseNumber) of $($TotalPhaseCount): Wireless was skipped because wireless management is disabled."
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
$Null = Write-Progress -Activity "Publishing the desired state to lab `"$($DesiredState.LabName)`"" -Id 40 -Completed
|
|
|
|
#region Summarize the outcome by entity type
|
|
$SummaryList = New-Object -TypeName 'System.Collections.Generic.List[PSObject]'
|
|
|
|
ForEach ($SummaryGroup In ($SyncResultList | Group-Object -Property 'EntityType' | Sort-Object -Property 'Name'))
|
|
{
|
|
$SummaryProperties = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary'
|
|
$SummaryProperties.EntityType = "$($SummaryGroup.Name)"
|
|
$SummaryProperties.Created = ($SummaryGroup.Group | Where-Object {($_.Action -ieq 'Created')} | Measure-Object).Count
|
|
$SummaryProperties.Updated = ($SummaryGroup.Group | Where-Object {($_.Action -ieq 'Updated')} | Measure-Object).Count
|
|
$SummaryProperties.Unchanged = ($SummaryGroup.Group | Where-Object {($_.Action -ieq 'Unchanged')} | Measure-Object).Count
|
|
$SummaryProperties.WhatIf = ($SummaryGroup.Group | Where-Object {($_.Action -ieq 'WhatIf')} | Measure-Object).Count
|
|
$SummaryProperties.Failed = ($SummaryGroup.Group | Where-Object {($_.Action -ieq 'Failed')} | Measure-Object).Count
|
|
|
|
$SummaryList.Add((New-Object -TypeName 'System.Management.Automation.PSObject' -Property ($SummaryProperties)))
|
|
}
|
|
#endregion
|
|
|
|
$PublicationProperties = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary'
|
|
$PublicationProperties.LabID = "$($LabID)"
|
|
$PublicationProperties.LabName = "$($DesiredState.LabName)"
|
|
$PublicationProperties.CompletedAt = [System.DateTime]::UtcNow
|
|
$PublicationProperties.SummaryList = $SummaryList
|
|
$PublicationProperties.ResultList = $SyncResultList
|
|
|
|
Write-Output -InputObject (New-Object -TypeName 'System.Management.Automation.PSObject' -Property ($PublicationProperties))
|
|
}
|
|
Catch
|
|
{
|
|
Throw
|
|
}
|
|
}
|
|
#endregion
|