├── .gitignore ├── F5-LTM ├── Public │ ├── Deprecated │ │ ├── Get-iRuleCollection.ps1 │ │ ├── Get-PoolList.ps1 │ │ ├── Get-PoolMemberCollection.ps1 │ │ ├── Get-F5session.ps1 │ │ ├── Get-VirtualServerList.ps1 │ │ ├── Get-PoolMemberCollectionStatus.ps1 │ │ ├── Get-PoolMemberIP.ps1 │ │ ├── Get-PoolMemberStatus.ps1 │ │ ├── Get-PoolMemberDescription.ps1 │ │ ├── Get-VirtualServeriRuleCollection.ps1 │ │ ├── Get-CurrentConnectionCount.ps1 │ │ └── Set-PoolLoadBalancingMode.ps1 │ ├── Remove-ProfileRamCache.ps1 │ ├── Get-F5Status.ps1 │ ├── Sync-DeviceToGroup.ps1 │ ├── Test-F5Session.ps1 │ ├── Get-HealthMonitorType.ps1 │ ├── Test-ProfileHttp.ps1 │ ├── Invoke-RestMethodOverride.ps1 │ ├── Test-HealthMonitor.ps1 │ ├── Test-Pool.ps1 │ ├── Get-StatusShape.ps1 │ ├── Test-VirtualServer.ps1 │ ├── Get-PoolStats.ps1 │ ├── Get-NodeStats.ps1 │ ├── Get-ProfileHttp.ps1 │ ├── Get-VirtualServerStats.ps1 │ ├── Get-BIGIPPartition.ps1 │ ├── Remove-iRule.ps1 │ ├── Get-Pool.ps1 │ ├── Remove-Pool.ps1 │ ├── Get-SSLCertificate.ps1 │ ├── Remove-ProfileHttp.ps1 │ ├── Remove-F5Session.ps1 │ ├── Get-iRule.ps1 │ ├── Add-PoolMonitor.ps1 │ ├── Test-Node.ps1 │ ├── Remove-VirtualServer.ps1 │ ├── Get-Node.ps1 │ ├── Enable-VirtualServer.ps1 │ ├── Disable-VirtualServer.ps1 │ ├── Remove-PoolMonitor.ps1 │ ├── Get-PoolMonitor.ps1 │ ├── Remove-Node.ps1 │ ├── Remove-HealthMonitor.ps1 │ ├── Enable-Node.ps1 │ ├── Get-PoolsForMember.ps1 │ ├── Get-HealthMonitor.ps1 │ ├── Enable-PoolMember.ps1 │ ├── Disable-Node.ps1 │ ├── Set-PoolMemberRatio.ps1 │ ├── Set-PoolMemberDescription.ps1 │ ├── Get-PoolMember.ps1 │ ├── Disable-PoolMember.ps1 │ ├── Remove-PoolMember.ps1 │ ├── Get-PoolMemberStats.ps1 │ ├── Add-iRuleToVirtualServer.ps1 │ ├── Remove-iRuleFromVirtualServer.ps1 │ ├── New-Pool.ps1 │ ├── New-HealthMonitor.ps1 │ ├── Get-VirtualServer.ps1 │ ├── New-Node.ps1 │ ├── Set-iRule.ps1 │ ├── Add-PoolMember.ps1 │ ├── New-ProfileHttp.ps1 │ ├── New-F5Session.ps1 │ ├── New-VirtualServer.ps1 │ ├── Test-Functionality.ps1 │ └── Set-Pool.ps1 ├── Validation.cs ├── Private │ ├── New-F5Item.ps1 │ ├── Get-ItemPath.ps1 │ ├── Resolve-NestedStats.ps1 │ ├── Invoke-NullCoalescing.ps1 │ ├── Invoke-F5RestMethod.ps1 │ ├── Add-ObjectDetail.ps1 │ ├── ArgumentCompletion.ps1 │ └── Join-Object.ps1 ├── F5-LTM.psm1 ├── F5-LTM.psd1 └── TypeData │ ├── PoshLTM.Types.ps1xml │ └── PoshLTM.Types.cs ├── .gitattributes ├── LICENSE ├── appveyor.yml └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | appveyor.yml 2 | F5-LTM/PSGetModuleInfo.xml 3 | -------------------------------------------------------------------------------- /F5-LTM/Public/Deprecated/Get-iRuleCollection.ps1: -------------------------------------------------------------------------------- 1 | Function Get-iRuleCollection { 2 | <# 3 | .SYNOPSIS 4 | Retrieve specified iRule(s) 5 | #> 6 | param( 7 | $F5Session=$Script:F5Session 8 | ) 9 | Write-Warning "Get-iRuleCollection is deprecated. Please use Get-iRule" 10 | Get-iRule -F5session $F5Session 11 | } -------------------------------------------------------------------------------- /F5-LTM/Public/Deprecated/Get-PoolList.ps1: -------------------------------------------------------------------------------- 1 | Function Get-PoolList { 2 | <# 3 | .SYNOPSIS 4 | Get-PoolList is deprecated. Please use Get-Pool | Select-Object -ExpandProperty fullPath 5 | #> 6 | param ( 7 | $F5Session=$Script:F5Session 8 | ) 9 | Write-Warning "Get-PoolList is deprecated. Please use Get-Pool | Select-Object -ExpandProperty fullPath" 10 | Get-Pool -F5Session $F5Session | Select-Object -ExpandProperty fullPath 11 | } -------------------------------------------------------------------------------- /F5-LTM/Public/Deprecated/Get-PoolMemberCollection.ps1: -------------------------------------------------------------------------------- 1 | Function Get-PoolMemberCollection { 2 | <# 3 | .SYNOPSIS 4 | Get-PoolMemberCollection is deprecated. Please use Get-PoolMember 5 | #> 6 | param( 7 | $F5Session=$Script:F5Session, 8 | [Parameter(Mandatory=$true)]$PoolName 9 | ) 10 | Write-Warning "Get-PoolMemberCollection is deprecated. Please use Get-PoolMember" 11 | Get-PoolMember -F5Session $F5Session -PoolName $PoolName 12 | } -------------------------------------------------------------------------------- /F5-LTM/Public/Deprecated/Get-F5session.ps1: -------------------------------------------------------------------------------- 1 | Function Get-F5session{ 2 | <# 3 | .SYNOPSIS 4 | Get-F5session is deprecated. Please use New-F5Session 5 | #> 6 | param( 7 | [Parameter(Mandatory=$true)][string]$LTMName, 8 | [Parameter(Mandatory=$true)][System.Management.Automation.PSCredential]$LTMCredentials 9 | ) 10 | Write-Warning "Get-F5session is deprecated. Please use New-F5Session" 11 | New-F5Session -LTMName $LTMName -LTMCredentials $LTMCredentials 12 | } -------------------------------------------------------------------------------- /F5-LTM/Public/Deprecated/Get-VirtualServerList.ps1: -------------------------------------------------------------------------------- 1 | Function Get-VirtualServerList{ 2 | <# 3 | .SYNOPSIS 4 | Get-VirtualServerList is deprecated. Please use Get-VirtualServer | Select-Object -ExpandProperty fullPath 5 | #> 6 | param ( 7 | $F5Session=$Script:F5Session 8 | ) 9 | Write-Warning "Get-VirtualServerList is deprecated. Please use Get-VirtualServer | Select-Object -ExpandProperty fullPath" 10 | Get-VirtualServer -F5Session $F5Session | Select-Object -ExpandProperty fullPath 11 | } -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set default behaviour, in case users don't have core.autocrlf set. 2 | * text=auto 3 | 4 | # Explicitly declare text files we want to always be normalized and converted 5 | # to native line endings on checkout. 6 | *.md text 7 | *.gitattributes text 8 | 9 | # Declare files that will always have CRLF line endings on checkout. 10 | *.cs text eol=crlf 11 | *.ps1 text eol=crlf 12 | *.psm1 text eol=crlf 13 | *.psd1 text eol=crlf 14 | *.psc1 text eol=crlf 15 | *.ps1xml text eol=crlf 16 | *.clixml text eol=crlf 17 | *.xml text eol=crlf 18 | *.txt text eol=crlf 19 | -------------------------------------------------------------------------------- /F5-LTM/Public/Deprecated/Get-PoolMemberCollectionStatus.ps1: -------------------------------------------------------------------------------- 1 | Function Get-PoolMemberCollectionStatus { 2 | <# 3 | .SYNOPSIS 4 | Get-PoolMemberCollectionStatus is deprecated. Please use Get-PoolMember | Select-Object -Property name,session,state 5 | #> 6 | param( 7 | $F5Session=$Script:F5Session, 8 | [Parameter(Mandatory=$true)]$PoolName 9 | ) 10 | Write-Warning "Get-PoolMemberCollectionStatus is deprecated. Please use Get-PoolMember | Select-Object -Property name,session,state" 11 | Get-PoolMember -F5Session $F5Session -PoolName $PoolName | Select-Object -Property name,session,state 12 | } -------------------------------------------------------------------------------- /F5-LTM/Public/Deprecated/Get-PoolMemberIP.ps1: -------------------------------------------------------------------------------- 1 | Function Get-PoolMemberIP { 2 | <# 3 | .SYNOPSIS 4 | Get-PoolMemberIP is deprecated. Please use Get-PoolMember | Select-Object -ExpandProperty address 5 | #> 6 | param( 7 | $F5Session=$Script:F5Session, 8 | [Parameter(Mandatory=$true)]$ComputerName, 9 | [Parameter(Mandatory=$true)]$PoolName 10 | ) 11 | Write-Warning "Get-PoolMemberIP is deprecated. Please use Get-PoolMember | Select-Object -ExpandProperty address" 12 | Get-PoolMember -F5Session $F5Session -Address $ComputerName -PoolName $PoolName | Select-Object -ExpandProperty address 13 | } -------------------------------------------------------------------------------- /F5-LTM/Public/Deprecated/Get-PoolMemberStatus.ps1: -------------------------------------------------------------------------------- 1 | Function Get-PoolMemberStatus { 2 | <# 3 | .SYNOPSIS 4 | Get-PoolMemberStatus is deprecated. Please use Get-PoolMember | Select-Object -Property name,session,state 5 | #> 6 | param( 7 | $F5Session=$Script:F5Session, 8 | [Parameter(Mandatory=$true)]$ComputerName, 9 | [Parameter(Mandatory=$true)]$PoolName 10 | ) 11 | Write-Warning "Get-PoolMemberStatus is deprecated. Please use Get-PoolMember | Select-Object -Property name,session,state" 12 | Get-PoolMember -F5Session $F5Session -PoolName $PoolName -Address $ComputerName | Select-Object -Property name,session,state 13 | } -------------------------------------------------------------------------------- /F5-LTM/Public/Deprecated/Get-PoolMemberDescription.ps1: -------------------------------------------------------------------------------- 1 | Function Get-PoolMemberDescription { 2 | <# 3 | .SYNOPSIS 4 | Get-PoolMemberDescription is deprecated. Please use Get-PoolMember | Select-Object -ExpandProperty description 5 | #> 6 | param( 7 | $F5Session=$Script:F5Session, 8 | [Parameter(Mandatory=$true)]$ComputerName, 9 | [Parameter(Mandatory=$true)]$PoolName 10 | ) 11 | Write-Warning "Get-PoolMemberDescription is deprecated. Please use Get-PoolMember | Select-Object -ExpandProperty description" 12 | Get-PoolMember -F5Session $F5Session -PoolName $PoolName -Address $ComputerName | Select-Object -ExpandProperty description 13 | } -------------------------------------------------------------------------------- /F5-LTM/Public/Remove-ProfileRamCache.ps1: -------------------------------------------------------------------------------- 1 | Function Remove-ProfileRamCache{ 2 | <# 3 | .SYNOPSIS 4 | Delete the contents of a RAM cache for the specified profile 5 | .NOTES 6 | Example profile: "profile/http/ramcache" 7 | #> 8 | [cmdletbinding()] 9 | param( 10 | $F5Session=$Script:F5Session, 11 | [Parameter(Mandatory=$true)]$ProfileName 12 | ) 13 | 14 | #Test that the F5 session is in a valid format 15 | Test-F5Session($F5Session) 16 | 17 | $ProfileURL = $F5Session.BaseURL +$ProfileName 18 | 19 | Invoke-F5RestMethod -Method DELETE -Uri "$ProfileURL" -F5Session $F5Session -ErrorMessage "Failed to clear the ram cache for the $ProfileName profile." | 20 | Out-Null 21 | } 22 | -------------------------------------------------------------------------------- /F5-LTM/Public/Get-F5Status.ps1: -------------------------------------------------------------------------------- 1 | Function Get-F5Status{ 2 | <# 3 | .SYNOPSIS 4 | Test whether the specified F5 is currently in active or standby failover mode 5 | #> 6 | [cmdletBinding()] 7 | param ( 8 | $F5Session=$Script:F5Session 9 | ) 10 | 11 | #Test that the F5 session is in a valid format 12 | Test-F5Session($F5Session) 13 | 14 | $FailoverPage = $F5Session.BaseURL -replace "/ltm/", "/cm/failover-status" 15 | 16 | $FailoverJSON = Invoke-F5RestMethod -Method Get -Uri $FailoverPage -F5Session $F5Session 17 | 18 | #This is where the failover status is indicated 19 | $FailoverJSON.entries.'https://localhost/mgmt/tm/cm/failover-status/0'.nestedStats.entries.status.description 20 | } 21 | -------------------------------------------------------------------------------- /F5-LTM/Public/Deprecated/Get-VirtualServeriRuleCollection.ps1: -------------------------------------------------------------------------------- 1 | Function Get-VirtualServeriRuleCollection { 2 | <# 3 | .SYNOPSIS 4 | Get-VirtualServeriRuleCollection is deprecated. Please use Get-VirtualServer | Select-Object -ExpandProperty rules 5 | #> 6 | 7 | param ( 8 | $F5Session=$Script:F5Session, 9 | [Alias("VirtualServerName")] 10 | [Parameter(Mandatory=$false,ValueFromPipeline=$true)] 11 | [string[]]$Name, 12 | [Parameter(Mandatory=$false)]$Partition 13 | ) 14 | process { 15 | 16 | Write-Warning "Get-VirtualServeriRuleCollection is deprecated. Please use Get-VirtualServer | Where-Object rules | Select-Object -ExpandProperty rules" 17 | Get-VirtualServer -F5Session $F5Session -Name $Name -Partition $Partition | Select-Object -ExpandProperty rules -ErrorAction SilentlyContinue 18 | } 19 | } -------------------------------------------------------------------------------- /F5-LTM/Public/Sync-DeviceToGroup.ps1: -------------------------------------------------------------------------------- 1 | Function Sync-DeviceToGroup { 2 | <# 3 | .SYNOPSIS 4 | Sync the specified device to the group. This assumes the F5 session object is for the device that will be synced to the group. 5 | #> 6 | [cmdletBinding()] 7 | param ( 8 | $F5Session=$Script:F5Session, 9 | [Parameter(Mandatory=$true)]$GroupName 10 | ) 11 | 12 | #Test that the F5 session is in a valid format 13 | Test-F5Session($F5Session) 14 | 15 | $URI = $F5Session.BaseURL -replace "/ltm", "/cm" 16 | 17 | $JSONBody = @{command='run';utilCmdArgs="config-sync to-group $GroupName"} 18 | $JSONBody = $JSONBody | ConvertTo-Json 19 | 20 | Invoke-F5RestMethod -Method POST -Uri "$URI" -F5Session $F5Session -Body $JSONBody -ContentType 'application/json' -ErrorMessage "Failed to sync the device to the $GroupName group" -AsBoolean 21 | } -------------------------------------------------------------------------------- /F5-LTM/Public/Test-F5Session.ps1: -------------------------------------------------------------------------------- 1 | Function Test-F5Session { 2 | <# 3 | .SYNOPSIS 4 | Check that the F5Session object has a valid base URL and PSCredential object 5 | #> 6 | [cmdletBinding()] 7 | param ( 8 | $F5Session=$Script:F5Session 9 | ) 10 | #Validate F5Session 11 | If ($($F5Session.BaseURL) -ne ("https://$($F5Session.Name)/mgmt/tm/ltm/") -or ($F5Session.WebSession.GetType().name -ne 'WebRequestSession')) { 12 | Write-Error 'You must either create an F5 Session with script scope (by calling New-F5Session with -passthrough parameter) or pass an F5 session to this function.' 13 | } 14 | # JN: Currently this function returns nothing if successful, which doesn't seem correct. However, if it returned true, all functions that call it would need to suppress the returned value 15 | # Else { 16 | # Return($true) 17 | # } 18 | } -------------------------------------------------------------------------------- /F5-LTM/Public/Get-HealthMonitorType.ps1: -------------------------------------------------------------------------------- 1 | Function Get-HealthMonitorType { 2 | <# 3 | .SYNOPSIS 4 | Retrieve the specified health monitor type(s). 5 | #> 6 | [cmdletBinding()] 7 | param ( 8 | $F5Session=$Script:F5Session, 9 | 10 | [Alias('Type')] 11 | [Parameter(Mandatory=$false,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] 12 | [string[]]$Name='*' 13 | ) 14 | begin { 15 | #Test that the F5 session is in a valid format 16 | Test-F5Session($F5Session) 17 | } 18 | process { 19 | foreach ($itemname in $Name) { 20 | $URI = $F5Session.BaseURL + 'monitor/' 21 | $JSON = Invoke-F5RestMethod -Method Get -Uri $URI -F5Session $F5Session 22 | $JSON.items.reference | ForEach-Object { 23 | [Regex]::Match($_.Link,'(?<=/)[^/?]*(?=\?)').Value | 24 | Where-Object { $_ -like $itemname } 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /F5-LTM/Public/Deprecated/Get-CurrentConnectionCount.ps1: -------------------------------------------------------------------------------- 1 | Function Get-CurrentConnectionCount { 2 | <# 3 | .SYNOPSIS 4 | Get-CurrentConnectionCount is deprecated. Please use Get-PoolMemberStats | Select-Object -ExpandProperty 'serverside.curConns' 5 | #> 6 | param( 7 | $F5Session=$Script:F5Session, 8 | 9 | [Parameter(Mandatory=$true)] 10 | [string[]]$PoolName, 11 | [Parameter(Mandatory=$false)] 12 | [string]$Partition, 13 | 14 | [Alias("ComputerName")] 15 | [Parameter(Mandatory=$false)] 16 | [string]$Address='0.0.0.0', 17 | 18 | [Parameter(Mandatory=$false)] 19 | [string]$Name='*' 20 | ) 21 | Write-Warning "Get-CurrentConnectionCount is deprecated. Please use Get-PoolMemberStats | Select-Object -ExpandProperty 'serverside.curConns'" 22 | Get-PoolMemberStats -F5Session $F5Session -PoolName $PoolName -Partition $Partition -Address $Address -Name $Name | Select-Object -ExpandProperty 'serverside.curConns' 23 | } -------------------------------------------------------------------------------- /F5-LTM/Validation.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Net; 3 | using System.Net.Security; 4 | using System.Security.Cryptography.X509Certificates; 5 | 6 | public static class SSLValidator 7 | { 8 | private static Stack funcs = new Stack(); 9 | 10 | private static bool OnValidateCertificate(object sender, X509Certificate certificate, X509Chain chain, 11 | SslPolicyErrors sslPolicyErrors) 12 | { 13 | return true; 14 | } 15 | 16 | public static void OverrideValidation() 17 | { 18 | funcs.Push(ServicePointManager.ServerCertificateValidationCallback); 19 | ServicePointManager.ServerCertificateValidationCallback = 20 | OnValidateCertificate; 21 | } 22 | 23 | public static void RestoreValidation() 24 | { 25 | if (funcs.Count > 0) { 26 | ServicePointManager.ServerCertificateValidationCallback = funcs.Pop(); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /F5-LTM/Public/Test-ProfileHttp.ps1: -------------------------------------------------------------------------------- 1 | Function Test-ProfileHttp { 2 | <# 3 | .SYNOPSIS 4 | Test whether the specified profile(s) exist 5 | .NOTES 6 | Profile names are case-specific. 7 | 8 | #> 9 | [cmdletBinding()] 10 | [OutputType([bool])] 11 | param ( 12 | $F5Session=$Script:F5Session, 13 | 14 | [Alias('ProfileName')] 15 | [Parameter(Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] 16 | [string[]]$Name, 17 | [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] 18 | [string]$Partition 19 | ) 20 | begin { 21 | #Test that the F5 session is in a valid format 22 | Test-F5Session($F5Session) 23 | 24 | Write-Verbose "NB: Profile names are case-specific." 25 | } 26 | process { 27 | foreach ($itemname in $Name) { 28 | $URI = $F5Session.BaseURL + 'profile/http/{0}' -f (Get-ItemPath -Name $itemname -Partition $Partition) 29 | Invoke-F5RestMethod -Method Get -Uri $URI -F5Session $F5Session -AsBoolean 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /F5-LTM/Public/Invoke-RestMethodOverride.ps1: -------------------------------------------------------------------------------- 1 | Function Invoke-RestMethodOverride { 2 | [cmdletBinding(DefaultParameterSetName='Anonymous')] 3 | [OutputType([Xml.XmlDocument])] 4 | [OutputType([Microsoft.PowerShell.Commands.BasicHtmlWebResponseObject])] 5 | [OutputType([String])] 6 | [OutputType([bool])] 7 | param ( 8 | [Parameter(Mandatory=$true)][Microsoft.PowerShell.Commands.WebRequestMethod]$Method, 9 | [Parameter(Mandatory=$true)][uri]$URI, 10 | 11 | [System.Management.Automation.PSCredential] 12 | $Credential, 13 | 14 | [Microsoft.PowerShell.Commands.WebRequestSession] 15 | $WebSession=(New-Object Microsoft.PowerShell.Commands.WebRequestSession), 16 | 17 | $Body, 18 | $Headers, 19 | $ContentType 20 | ) 21 | if ($PSVersionTable.PSVersion.Major -ge 6) { 22 | Invoke-RestMethod @PSBoundParameters -SkipCertificateCheck -NoProxy 23 | } 24 | else { 25 | [SSLValidator]::OverrideValidation() 26 | Invoke-RestMethod @PSBoundParameters 27 | [SSLValidator]::RestoreValidation() 28 | } 29 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Joel Newton 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /F5-LTM/Private/New-F5Item.ps1: -------------------------------------------------------------------------------- 1 | Function New-F5Item { 2 | [cmdletbinding(SupportsShouldProcess=$True)] 3 | param ( 4 | [Parameter(Mandatory=$true)] 5 | [string]$Name, 6 | [string]$Application, 7 | [string]$Partition 8 | ) 9 | $ItemPath = Get-ItemPath -Name $Name -Application $Application -Partition $Partition 10 | If ($ItemPath -match '^[~/](?[^~/]*)[~/]((?[^~/]*).app[~/])?(?[^~/]*)$') { 11 | if ($matches['Application']) { 12 | [pscustomobject]@{ 13 | application = $matches['Application'] 14 | name = $matches['Name'] 15 | partition = $matches['Partition'] 16 | fullPath = $ItemPath -replace '~','/' 17 | itempath = $ItemPath 18 | } 19 | } else { 20 | [pscustomobject]@{ 21 | name = $matches['Name'] 22 | partition = $matches['Partition'] 23 | fullPath = $ItemPath -replace '~','/' 24 | itempath = $ItemPath 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /F5-LTM/Private/Get-ItemPath.ps1: -------------------------------------------------------------------------------- 1 | Function Get-ItemPath { 2 | param ( 3 | [Parameter(Mandatory=$false)][string]$Name, 4 | [Parameter(Mandatory=$false)][string]$Application, 5 | [Parameter(Mandatory=$false)][string]$Partition 6 | ) 7 | if ($Name -match '^[~/].*[~/]([^~/]*.app[~/])?.*$') { 8 | $Name -replace '/','~' 9 | } else { 10 | if ([string]::IsNullOrEmpty($Name)) { 11 | if ([string]::IsNullOrEmpty($Partition)) { 12 | '' 13 | } else { 14 | "?`$filter=partition eq $Partition" 15 | } 16 | } else { 17 | if ([string]::IsNullOrEmpty($Partition)) { 18 | if ([string]::IsNullOrEmpty($Application)) { 19 | "~Common~$Name" 20 | } 21 | else { 22 | "~Common~$Application.app~$Name" 23 | } 24 | } else { 25 | if ([string]::IsNullOrEmpty($Application)) { 26 | "~$Partition~$Name" 27 | } 28 | else { 29 | "~$Partition~$Application.app~$Name" 30 | } 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /F5-LTM/Public/Test-HealthMonitor.ps1: -------------------------------------------------------------------------------- 1 | Function Test-HealthMonitor { 2 | <# 3 | .SYNOPSIS 4 | Test whether the specified health monitor(s) exist 5 | .NOTES 6 | HealthMonitor names are case-specific. 7 | #> 8 | [cmdletBinding()] 9 | [OutputType([bool])] 10 | param ( 11 | $F5Session=$Script:F5Session, 12 | 13 | [Alias('MonitorName')] 14 | [Parameter(Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] 15 | [string[]]$Name, 16 | 17 | [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] 18 | [string]$Partition, 19 | 20 | [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)] 21 | [string]$Type 22 | ) 23 | begin { 24 | #Test that the F5 session is in a valid format 25 | Test-F5Session($F5Session) 26 | 27 | Write-Verbose "NB: HealthMonitor names are case-specific." 28 | } 29 | process { 30 | foreach ($itemname in $Name) { 31 | $URI = $F5Session.BaseURL + 'monitor/{0}/{1}' -f $Type,(Get-ItemPath -Name $itemname -Partition $Partition) 32 | Invoke-F5RestMethod -Method Get -Uri $URI -F5Session $F5Session -AsBoolean 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /F5-LTM/Public/Test-Pool.ps1: -------------------------------------------------------------------------------- 1 | Function Test-Pool { 2 | <# 3 | .SYNOPSIS 4 | Test whether the specified pool(s) exist 5 | .NOTES 6 | Pool names are case-specific. 7 | #> 8 | [cmdletBinding()] 9 | [OutputType([bool])] 10 | param ( 11 | $F5Session=$Script:F5Session, 12 | 13 | [Alias('PoolName')] 14 | [Parameter(Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] 15 | [string[]]$Name, 16 | 17 | [Alias('iApp')] 18 | [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] 19 | [string]$Application, 20 | 21 | [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] 22 | [string]$Partition 23 | ) 24 | begin { 25 | #Test that the F5 session is in a valid format 26 | Test-F5Session($F5Session) 27 | 28 | Write-Verbose "NB: Pool names are case-specific." 29 | } 30 | process { 31 | foreach ($itemname in $Name) { 32 | $URI = $F5Session.BaseURL + 'pool/{0}' -f (Get-ItemPath -Name $itemname -Application $Application -Partition $Partition) 33 | Invoke-F5RestMethod -Method Get -Uri $URI -F5Session $F5Session -AsBoolean 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /F5-LTM/Private/Resolve-NestedStats.ps1: -------------------------------------------------------------------------------- 1 | Function Resolve-NestedStats { 2 | <# 3 | .SYNOPSIS 4 | Between v11.6 and v12.0, there were breaking changes in regards to the JSON format returned for various iControlREST requests, such as when retrieving the system version or pool member stats. 5 | Specifically, instead of the data existing in an "entries" property directly underneath the parent JSON object, it is now enclosed in "nestedStats" property within a custom PS object whose name resembles a self-link with the member name repeated. 6 | 7 | To resolve this discrepancy, this function performs version-specific transformations to the JSON data and returns it in a standardized format with the "entries" property at the top. 8 | 9 | JN: It's unclear to me how the JSON parameter, cast as a string, can have a property called 'entries.' 10 | #> 11 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "")] 12 | param ( 13 | $F5Session=$Script:F5Session, 14 | 15 | [Parameter(Mandatory=$true)][string]$JSON 16 | ) 17 | switch ($F5Session.LTMVersion.Major) 18 | { 19 | { $_ -gt 11 } { $JSON = $JSON.entries.PSObject.Properties.Value.nestedStats; } 20 | Default { <# assume no conversion needed #> } 21 | } 22 | 23 | $JSON 24 | } -------------------------------------------------------------------------------- /F5-LTM/Public/Get-StatusShape.ps1: -------------------------------------------------------------------------------- 1 | Function Get-StatusShape { 2 | <# 3 | .SYNOPSIS 4 | Determine the shape to display for a member's current state and session values 5 | #> 6 | [cmdletBinding()] 7 | [OutputType([String])] 8 | param( 9 | [Parameter(Mandatory=$true)]$state, 10 | [Parameter(Mandatory=$true)]$session 11 | ) 12 | 13 | #Determine status shape based on state and session values 14 | If ($state -eq "up" -and $session -eq "monitor-enabled"){ 15 | $StatusShape = "green-circle" 16 | } 17 | ElseIf ($state -eq "up" -and $session -eq "user-disabled"){ 18 | $StatusShape = "black-circle" 19 | } 20 | ElseIf ($state -eq "user-down" -and $session -eq "user-disabled"){ 21 | $StatusShape = "black-diamond" 22 | } 23 | ElseIf ($state -eq "down" -and $session -eq "monitor-enabled"){ 24 | $StatusShape = "red-diamond" 25 | } 26 | ElseIf ($state -eq "unchecked" -and $session -eq "user-enabled"){ 27 | $StatusShape = "blue-square" 28 | } 29 | ElseIf ($state -eq "unchecked" -and $session -eq "user-disabled"){ 30 | $StatusShape = "black-square" 31 | } 32 | Else{ 33 | #Unknown 34 | $StatusShape = "black-square" 35 | } 36 | 37 | $StatusShape 38 | } 39 | -------------------------------------------------------------------------------- /F5-LTM/Public/Test-VirtualServer.ps1: -------------------------------------------------------------------------------- 1 | Function Test-VirtualServer { 2 | <# 3 | .SYNOPSIS 4 | Test whether the specified virtual server(s) exist 5 | .NOTES 6 | Virtual server names are case-specific. 7 | #> 8 | [cmdletBinding()] 9 | [OutputType([bool])] 10 | param ( 11 | $F5Session=$Script:F5Session, 12 | 13 | [Alias("VirtualServerName")] 14 | [Parameter(Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] 15 | [string[]]$Name, 16 | 17 | [Alias('iApp')] 18 | [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] 19 | [string]$Application, 20 | 21 | [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] 22 | [string]$Partition 23 | ) 24 | begin { 25 | #Test that the F5 session is in a valid format 26 | Test-F5Session($F5Session) 27 | 28 | Write-Verbose "NB: Virtual server names are case-specific." 29 | } 30 | process { 31 | foreach ($itemname in $Name) { 32 | $URI = $F5Session.BaseURL + 'virtual/{0}' -f (Get-ItemPath -Name $itemname -Application $Application -Partition $Partition) 33 | Invoke-F5RestMethod -Method Get -Uri $URI -F5Session $F5Session -AsBoolean 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /F5-LTM/Public/Get-PoolStats.ps1: -------------------------------------------------------------------------------- 1 | Function Get-PoolStats { 2 | <# 3 | .SYNOPSIS 4 | Retrieve specified Node(s) statistics 5 | .NOTES 6 | Node names are case-specific. 7 | #> 8 | [cmdletBinding()] 9 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "")] 10 | param ( 11 | $F5Session=$Script:F5Session, 12 | 13 | [Parameter(Mandatory=$false)] 14 | [string]$Partition, 15 | 16 | [Alias('PoolName')] 17 | [Parameter(Mandatory=$true,ValueFromPipeline,ValueFromPipelineByPropertyName)] 18 | [string[]]$Name='' 19 | 20 | ) 21 | begin { 22 | #Test that the F5 session is in a valid format 23 | Test-F5Session($F5Session) 24 | 25 | Write-Verbose "NB: Node names are case-specific." 26 | } 27 | process { 28 | for($i=0; $i -lt $Name.Count; $i++) { 29 | $itemname = Invoke-NullCoalescing {$Name[$i]} {''} 30 | $URI = $F5Session.BaseURL + 'pool/{0}/stats' -f (Get-ItemPath -Name $itemname -Partition $Partition) 31 | $JSON = Invoke-F5RestMethod -Method Get -Uri $URI -F5Session $F5Session 32 | $JSON = Resolve-NestedStats -F5Session $F5Session -JSONData $JSON 33 | Invoke-NullCoalescing {$JSON.entries} {$JSON} | Add-ObjectDetail 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /F5-LTM/Public/Get-NodeStats.ps1: -------------------------------------------------------------------------------- 1 | Function Get-NodeStats { 2 | <# 3 | .SYNOPSIS 4 | Retrieve specified Node(s) statistics 5 | .NOTES 6 | Node names are case-specific. 7 | #> 8 | [cmdletBinding()] 9 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "")] 10 | param ( 11 | $F5Session=$Script:F5Session, 12 | 13 | [Parameter(Mandatory=$false)] 14 | [string]$Partition, 15 | 16 | [Alias('ComputerName')] 17 | [Alias('NodeName')] 18 | [Parameter(Mandatory=$true,ValueFromPipeline,ValueFromPipelineByPropertyName)] 19 | [string[]]$Name='' 20 | ) 21 | begin { 22 | #Test that the F5 session is in a valid format 23 | Test-F5Session($F5Session) 24 | 25 | Write-Verbose "NB: Node names are case-specific." 26 | } 27 | process { 28 | for($i=0; $i -lt $Name.Count; $i++) { 29 | $itemname = Invoke-NullCoalescing {$Name[$i]} {''} 30 | $URI = $F5Session.BaseURL + 'node/{0}/stats' -f (Get-ItemPath -Name $itemname -Partition $Partition) 31 | $JSON = Invoke-F5RestMethod -Method Get -Uri $URI -F5Session $F5Session 32 | $JSON = Resolve-NestedStats -F5Session $F5Session -JSONData $JSON 33 | Invoke-NullCoalescing {$JSON.entries} {$JSON} | Add-ObjectDetail 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /F5-LTM/Public/Get-ProfileHttp.ps1: -------------------------------------------------------------------------------- 1 | Function Get-ProfileHttp { 2 | <# 3 | .SYNOPSIS 4 | Retrieve specified Profile(s) 5 | .NOTES 6 | Profil names are case-specific. 7 | .EXAMPLE 8 | Get-Profilehttp -F5Session $F5Session -Name $ProfilName -Partition $Partition 9 | #> 10 | [cmdletBinding()] 11 | param ( 12 | $F5Session=$Script:F5Session, 13 | 14 | [Alias('ProfileName')] 15 | [Parameter(Mandatory=$false,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] 16 | [string[]]$Name='', 17 | [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] 18 | [string]$Partition 19 | ) 20 | begin { 21 | #Test that the F5 session is in a valid format 22 | Test-F5Session($F5Session) 23 | 24 | Write-Verbose "NB: Pool names are case-specific." 25 | } 26 | process { 27 | foreach ($itemname in $Name) { 28 | $URI = $F5Session.BaseURL + 'profile/http/{0}' -f (Get-ItemPath -Name $itemname -Partition $Partition) 29 | $JSON = Invoke-F5RestMethod -Method Get -Uri $URI -F5Session $F5Session 30 | if ($JSON.items -or $JSON.name) { 31 | $items = Invoke-NullCoalescing {$JSON.items} {$JSON} 32 | $items | Add-ObjectDetail -TypeName 'PoshLTM.ProfileHttp' 33 | } 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /F5-LTM/Public/Get-VirtualServerStats.ps1: -------------------------------------------------------------------------------- 1 | Function Get-VirtualServerStats { 2 | <# 3 | .SYNOPSIS 4 | Retrieve specified Node(s) statistics 5 | .NOTES 6 | Node names are case-specific. 7 | #> 8 | [cmdletBinding()] 9 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "")] 10 | param ( 11 | $F5Session=$Script:F5Session, 12 | 13 | [Parameter(Mandatory=$false)] 14 | [string]$Partition, 15 | 16 | [Alias('VirtualServerName')] 17 | [Alias('VirtualName')] 18 | [Parameter(Mandatory=$true,ValueFromPipeline,ValueFromPipelineByPropertyName)] 19 | [string[]]$Name='' 20 | 21 | ) 22 | begin { 23 | #Test that the F5 session is in a valid format 24 | Test-F5Session($F5Session) 25 | 26 | Write-Verbose "NB: Node names are case-specific." 27 | } 28 | process { 29 | for($i=0; $i -lt $Name.Count; $i++) { 30 | $itemname = Invoke-NullCoalescing {$Name[$i]} {''} 31 | $URI = $F5Session.BaseURL + 'virtual/{0}/stats' -f (Get-ItemPath -Name $itemname -Partition $Partition) 32 | $JSON = Invoke-F5RestMethod -Method Get -Uri $URI -F5Session $F5Session 33 | $JSON = Resolve-NestedStats -F5Session $F5Session -JSONData $JSON 34 | Invoke-NullCoalescing {$JSON.entries} {$JSON} | Add-ObjectDetail 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /F5-LTM/Public/Get-BIGIPPartition.ps1: -------------------------------------------------------------------------------- 1 | Function Get-BIGIPPartition { 2 | <# 3 | .SYNOPSIS 4 | Retrieve specified Partition(s) 5 | .NOTES 6 | Partition names are case-specific. 7 | #> 8 | [cmdletBinding()] 9 | param ( 10 | $F5Session=$Script:F5Session, 11 | 12 | [Alias('Folder')] 13 | [Alias('Partition')] 14 | [Parameter(Mandatory=$false,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] 15 | [string[]]$Name='' 16 | ) 17 | begin { 18 | #Test that the F5 session is in a valid format 19 | Test-F5Session($F5Session) 20 | 21 | Write-Verbose "NB: Partition names are case-specific." 22 | } 23 | process { 24 | foreach ($itemname in $Name) { 25 | $itemname = $itemname -replace '/','~' 26 | if ($itemname -and ($itemname -notmatch '^~')) { 27 | $itemname = "~$itemname" 28 | } 29 | $URI = ($F5Session.BaseURL -replace 'ltm/','sys/folder') + '/{0}?$select=name,subPath' -f $itemname 30 | $JSON = Invoke-F5RestMethod -Method Get -Uri $URI -F5Session $F5Session 31 | Invoke-NullCoalescing {$JSON.items} {$JSON} | 32 | Where-Object { $_.subPath -eq '/' -and ([string]::IsNullOrEmpty($Name) -or $Name -contains $_.name) } | 33 | Select-Object -ExpandProperty name 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /F5-LTM/Public/Remove-iRule.ps1: -------------------------------------------------------------------------------- 1 | Function Remove-iRule { 2 | <# 3 | .SYNOPSIS 4 | Remove the specified irule(s). Confirmation is needed. 5 | #> 6 | [cmdletBinding( SupportsShouldProcess=$true, ConfirmImpact='High')] 7 | param ( 8 | $F5Session=$Script:F5Session, 9 | 10 | [Alias('iRule')] 11 | [Parameter(Mandatory=$true,ParameterSetName='InputObject',ValueFromPipeline=$true)] 12 | [PSObject[]]$InputObject, 13 | 14 | [Alias('iRuleName')] 15 | [Parameter(Mandatory=$true,ParameterSetName='Name',ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] 16 | [string[]]$Name, 17 | 18 | [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] 19 | [string]$Partition 20 | ) 21 | begin { 22 | #Test that the F5 session is in a valid format 23 | Test-F5Session($F5Session) 24 | 25 | Write-Verbose "NB: iRule names are case-specific." 26 | } 27 | process { 28 | switch($PSCmdLet.ParameterSetName) { 29 | InputObject { 30 | foreach($item in $InputObject) { 31 | if ($pscmdlet.ShouldProcess($item.fullPath)){ 32 | $URI = $F5Session.GetLink($item.selfLink) 33 | Invoke-F5RestMethod -Method DELETE -Uri $URI -F5Session $F5Session 34 | } 35 | } 36 | } 37 | Name { 38 | $Name | Get-iRule -F5Session $F5Session -Partition $Partition | Remove-iRule -F5session $F5Session 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /F5-LTM/Public/Get-Pool.ps1: -------------------------------------------------------------------------------- 1 | Function Get-Pool { 2 | <# 3 | .SYNOPSIS 4 | Retrieve specified pool(s) 5 | .NOTES 6 | Pool names are case-specific. 7 | #> 8 | [cmdletBinding()] 9 | param ( 10 | $F5Session=$Script:F5Session, 11 | 12 | [Alias('PoolName')] 13 | [Parameter(Mandatory=$false,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] 14 | [string[]]$Name='', 15 | 16 | [Alias('iApp')] 17 | [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] 18 | [string]$Application='', 19 | 20 | [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] 21 | [string]$Partition 22 | ) 23 | begin { 24 | #Test that the F5 session is in a valid format 25 | Test-F5Session($F5Session) 26 | 27 | Write-Verbose "NB: Pool names are case-specific." 28 | } 29 | process { 30 | foreach ($itemname in $Name) { 31 | $URI = $F5Session.BaseURL + 'pool/{0}' -f (Get-ItemPath -Name $itemname -Application $Application -Partition $Partition) 32 | $JSON = Invoke-F5RestMethod -Method Get -Uri $URI -F5Session $F5Session 33 | if ($JSON.items -or $JSON.name) { 34 | $items = Invoke-NullCoalescing {$JSON.items} {$JSON} 35 | if(![string]::IsNullOrWhiteSpace($Application)) { 36 | $items = $items | Where-Object {$_.fullPath -eq "/$($_.partition)/$Application.app/$($_.name)"} 37 | } 38 | $items | Add-ObjectDetail -TypeName 'PoshLTM.Pool' 39 | } 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /F5-LTM/Public/Remove-Pool.ps1: -------------------------------------------------------------------------------- 1 | Function Remove-Pool { 2 | <# 3 | .SYNOPSIS 4 | Remove the specified pool(s). Confirmation is needed. 5 | .NOTES 6 | Pool names are case-specific. 7 | #> 8 | [cmdletBinding( SupportsShouldProcess=$true, ConfirmImpact='High')] 9 | param ( 10 | $F5Session=$Script:F5Session, 11 | 12 | [Alias('Pool')] 13 | [Parameter(Mandatory=$true,ParameterSetName='InputObject',ValueFromPipeline=$true)] 14 | [PSObject[]]$InputObject, 15 | 16 | [Alias('PoolName')] 17 | [Parameter(Mandatory=$true,ParameterSetName='Name',ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] 18 | [string[]]$Name, 19 | 20 | [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] 21 | [string]$Partition 22 | ) 23 | begin { 24 | #Test that the F5 session is in a valid format 25 | Test-F5Session($F5Session) 26 | 27 | Write-Verbose "NB: Pool names are case-specific." 28 | } 29 | process { 30 | switch($PSCmdLet.ParameterSetName) { 31 | InputObject { 32 | foreach($item in $InputObject) { 33 | if ($pscmdlet.ShouldProcess($item.fullPath)){ 34 | $URI = $F5Session.GetLink($item.selfLink) 35 | Invoke-F5RestMethod -Method DELETE -Uri $URI -F5Session $F5Session 36 | } 37 | } 38 | } 39 | Name { 40 | $Name | Get-Pool -F5Session $F5Session -Partition $Partition | Remove-Pool -F5session $F5Session 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /F5-LTM/Public/Deprecated/Set-PoolLoadBalancingMode.ps1: -------------------------------------------------------------------------------- 1 | Function Set-PoolLoadBalancingMode { 2 | <# 3 | .SYNOPSIS 4 | Set-PoolLoadBalancingMode is deprecated. Please use Set-Pool 5 | #> 6 | [cmdletbinding()] 7 | param( 8 | $F5Session=$Script:F5Session, 9 | 10 | [Parameter(Mandatory=$true,ParameterSetName='InputObject',ValueFromPipeline=$true)] 11 | [Alias('Pool')] 12 | [PSObject[]]$InputObject, 13 | 14 | [Parameter(Mandatory=$true,ParameterSetName='PoolName',ValueFromPipeline=$true)] 15 | [string[]]$PoolName, 16 | 17 | [Parameter(Mandatory=$false,ParameterSetName='PoolName')] 18 | [string]$Partition, 19 | 20 | [Alias('LoadBalancingMode')] 21 | [Parameter(Mandatory=$true)] 22 | [ValidateSet("dynamic-ratio-member","dynamic-ratio-node","fastest-app-response","fastest-node","least-connections-members","least-connections-node","least-sessions","observed-member","observed-node","predictive-member","predictive-node","ratio-least-connections-member","ratio-least-connections-node","ratio-member","ratio-node","ratio-session","round-robin","weighted-least-connections-member","weighted-least-connections-node")] 23 | [string]$Name 24 | ) 25 | process { 26 | switch($PSCmdLet.ParameterSetName) { 27 | InputObject { 28 | $InputObject | Set-Pool -F5Session $F5Session -LoadBalancingMode $Name 29 | } 30 | PoolName { 31 | Get-Pool -F5Session $F5Session -Name $PoolName -Partition $Partition | Set-Pool -F5Session $F5Session -LoadBalancingMode $Name 32 | } 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /F5-LTM/Public/Get-SSLCertificate.ps1: -------------------------------------------------------------------------------- 1 | Function Get-SSLCertificate { 2 | <# 3 | .SYNOPSIS 4 | Get the SSL Certificates from the device 5 | 6 | .NOTES 7 | Only returns expired or expiring certs at this stage. 8 | #> 9 | [CmdletBinding()] 10 | Param ( 11 | $F5Session=$Script:F5Session 12 | ) 13 | 14 | #Test that the F5 session is in a valid format 15 | Test-F5Session($F5Session) 16 | 17 | $URI = $F5Session.BaseURL -replace "/ltm", "/sys" 18 | 19 | $JSONBody = @{command='run';utilCmdArgs="crypto check-cert verbose enabled"} 20 | $JSONBody = $JSONBody | ConvertTo-Json 21 | 22 | $JSON = Invoke-F5RestMethod -Method POST -Uri "$URI" -F5Session $F5Session -Body $JSONBody -ContentType 'application/json' -ErrorMessage "Failed to retrieve SSL Certs" 23 | 24 | # Regex returns expiring / or expired certss fully appreciate room for improvement 25 | $pattern = "CN=(?.*?),.*? in file (?.*?) (?expired on|will expire on) (?\w{3} \d{1,2} \d{2}:\d{2}:\d{2} \d{4}) ?(?:GMT)?" 26 | 27 | $matches = [regex]::Matches($json.CommandResult, $pattern) 28 | 29 | ForEach ($match in $matches) { 30 | $status = if ($match.Groups["Status"].Value -eq "expired on") { "expired" } else { "expiring" } 31 | 32 | [PSCustomObject]@{ 33 | CommonName = $match.Groups["CommonName"].Value 34 | FilePath = $match.Groups["FilePath"].Value 35 | ExpiryDate = [datetime]::ParseExact($match.Groups["ExpiryDate"].Value, "MMM dd HH:mm:ss yyyy", [System.Globalization.CultureInfo]::InvariantCulture) 36 | Status = $status 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /F5-LTM/Public/Remove-ProfileHttp.ps1: -------------------------------------------------------------------------------- 1 | Function Remove-ProfileHttp { 2 | <# 3 | .SYNOPSIS 4 | Remove the specified pool(s). Confirmation is needed. 5 | .NOTES 6 | ProfileHttp names are case-specific. 7 | #> 8 | [cmdletBinding( SupportsShouldProcess=$true, ConfirmImpact='High')] 9 | param ( 10 | $F5Session=$Script:F5Session, 11 | 12 | [Alias('ProfileHttp')] 13 | [Parameter(Mandatory=$true,ParameterSetName='InputObject',ValueFromPipeline=$true)] 14 | [PSObject[]]$InputObject, 15 | 16 | [Alias('ProfileHttpName')] 17 | [Parameter(Mandatory=$true,ParameterSetName='Name',ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] 18 | [string[]]$Name, 19 | 20 | [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] 21 | [string]$Partition 22 | ) 23 | begin { 24 | #Test that the F5 session is in a valid format 25 | Test-F5Session($F5Session) 26 | 27 | Write-Verbose "NB: ProfileHttp names are case-specific." 28 | } 29 | process { 30 | switch($PSCmdLet.ParameterSetName) { 31 | InputObject { 32 | foreach($item in $InputObject) { 33 | if ($pscmdlet.ShouldProcess($item.fullPath)){ 34 | $URI = $F5Session.GetLink($item.selfLink) 35 | Invoke-F5RestMethod -Method DELETE -Uri $URI -F5Session $F5Session 36 | } 37 | } 38 | } 39 | Name { 40 | $Name | Get-ProfileHttp -F5Session $F5Session -Partition $Partition | Remove-ProfileHttp -F5session $F5Session 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /F5-LTM/Public/Remove-F5Session.ps1: -------------------------------------------------------------------------------- 1 | Function Remove-F5Session { 2 | <# 3 | .SYNOPSIS 4 | Remove F5 session based on the token 5 | #> 6 | [cmdletBinding()] 7 | param ( 8 | $F5Session = $Script:F5Session, 9 | [switch]$SkipCertificateCheck 10 | ) 11 | #Validate F5Session 12 | Test-F5Session($F5Session) 13 | 14 | try { 15 | 16 | if ($SkipCertificateCheck) { 17 | 18 | if ($PSVersionTable.PSVersion.Major -ge 6) { 19 | 20 | $RemoveSession = Invoke-RestMethod "https://$($F5Session.name)/mgmt/shared/authz/tokens/$($F5Session.token)" -Headers @{'X-F5-Auth-Token' = $F5Session.token } -Method DELETE -SkipCertificateCheck -ErrorVariable LTMError -NoProxy 21 | } 22 | else { 23 | 24 | [SSLValidator]::OverrideValidation() 25 | $RemoveSession = Invoke-RestMethod "https://$($F5Session.name)/mgmt/shared/authz/tokens/$($F5Session.token)" -Headers @{'X-F5-Auth-Token' = $F5Session.token } -Method DELETE -ErrorVariable LTMError 26 | [SSLValidator]::RestoreValidation() 27 | } 28 | } 29 | else { 30 | 31 | $RemoveSession = Invoke-RestMethod "https://$($F5Session.name)/mgmt/shared/authz/tokens/$($F5Session.token)" -Headers @{'X-F5-Auth-Token' = $F5Session.token } -Method DELETE -ErrorVariable LTMError -NoProxy 32 | } 33 | 34 | Write-Verbose "Session : token $($RemoveSession.token) deleted" 35 | Remove-Variable F5Session -Scope Script 36 | Return($true) 37 | } 38 | 39 | catch { 40 | 41 | $ErrorMessage = $LTMError[0].message; 42 | Throw ("We failed to remove the specified session. The error was: $ErrorMessage") 43 | 44 | } 45 | } -------------------------------------------------------------------------------- /F5-LTM/Public/Get-iRule.ps1: -------------------------------------------------------------------------------- 1 | Function Get-iRule { 2 | <# 3 | .SYNOPSIS 4 | Retrieve specified iRule(s) 5 | .NOTES 6 | iRule names are case-specific. 7 | #> 8 | [cmdletBinding()] 9 | param( 10 | $F5Session=$Script:F5Session, 11 | 12 | [Alias("iRuleName")] 13 | [Parameter(Mandatory=$false,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] 14 | [string[]]$Name='', 15 | 16 | [Alias('iApp')] 17 | [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] 18 | [string]$Application='', 19 | 20 | [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] 21 | [string]$Partition 22 | ) 23 | begin { 24 | #Test that the F5 session is in a valid format 25 | Test-F5Session($F5Session) 26 | 27 | Write-Verbose "NB: iRule names are case-specific." 28 | } 29 | process { 30 | foreach ($itemname in $Name) { 31 | $URI = $F5Session.BaseURL + 'rule/{0}' -f (Get-ItemPath -Name $itemname -Application $Application -Partition $Partition) 32 | $JSON = Invoke-F5RestMethod -Method Get -Uri $URI -F5Session $F5Session 33 | if ($JSON.items -or $JSON.name) { 34 | $items = Invoke-NullCoalescing {$JSON.items} {$JSON} 35 | if(![string]::IsNullOrWhiteSpace($Application) -and ![string]::IsNullOrWhiteSpace($Partition)) { 36 | $items = $items | Where-Object {$_.fullPath -eq "/$($_.partition)/$Application.app/$($_.name)"} 37 | } 38 | $items | Add-ObjectDetail -TypeName 'PoshLTM.iRule' | Add-Member -MemberType AliasProperty -Name iRuleContent -Value apiAnonymous; 39 | $items 40 | } 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /F5-LTM/Public/Add-PoolMonitor.ps1: -------------------------------------------------------------------------------- 1 | Function Add-PoolMonitor { 2 | <# 3 | .SYNOPSIS 4 | Add a health monitor to a pool 5 | #> 6 | [cmdletBinding()] 7 | param( 8 | $F5Session=$Script:F5Session, 9 | 10 | [Parameter(Mandatory=$true,ParameterSetName='InputObject',ValueFromPipeline=$true)] 11 | [Alias('Pool')] 12 | [PSObject[]]$InputObject, 13 | 14 | [Parameter(Mandatory=$true,ParameterSetName='PoolName',ValueFromPipeline=$true)] 15 | [string[]]$PoolName, 16 | 17 | [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] 18 | [string]$Partition, 19 | 20 | [Alias('MonitorName')] 21 | [Parameter(Mandatory=$true)] 22 | [string[]]$Name 23 | ) 24 | begin { 25 | #Test that the F5 session is in a valid format 26 | Test-F5Session($F5Session) 27 | } 28 | process { 29 | switch($PSCmdLet.ParameterSetName) { 30 | InputObject { 31 | $InputObject | ForEach-Object { 32 | $JSONBody = @{ 33 | monitor=(([string[]]$($_.monitor -split ' and ') + $Name | Where-Object { $_ } | ForEach-Object { [Regex]::Match($_.Trim(), '[^/\s]*$').Value } | Select-Object -Unique) -join ' and ') 34 | } | ConvertTo-Json 35 | $URI = $F5Session.GetLink($InputObject.selfLink) 36 | Invoke-F5RestMethod -Method PATCH -Uri "$URI" -F5Session $F5Session -Body $JSONBody -ContentType 'application/json' 37 | } 38 | } 39 | PoolName { 40 | Get-Pool -F5Session $F5Session -Name $PoolName -Partition $Partition | Add-PoolMonitor -F5Session $F5Session -Name $Name 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /F5-LTM/Public/Test-Node.ps1: -------------------------------------------------------------------------------- 1 | Function Test-Node { 2 | <# 3 | .SYNOPSIS 4 | Test whether the specified node(s) exist 5 | .NOTES 6 | Node names are case-specific. 7 | #> 8 | [cmdletBinding(DefaultParameterSetName='Address')] 9 | [OutputType([bool])] 10 | param ( 11 | $F5Session=$Script:F5Session, 12 | 13 | [Parameter(Mandatory,ParameterSetName='Address',ValueFromPipelineByPropertyName)] 14 | [Parameter(Mandatory,ParameterSetName='AddressAndName',ValueFromPipelineByPropertyName)] 15 | [PoshLTM.F5Address[]]$Address=[PoshLTM.F5Address]::Any, 16 | 17 | [Alias('ComputerName')] 18 | [Alias('NodeName')] 19 | [Parameter(Mandatory,ParameterSetName='Name',ValueFromPipeline,ValueFromPipelineByPropertyName)] 20 | [Parameter(Mandatory,ParameterSetName='AddressAndName',ValueFromPipeline,ValueFromPipelineByPropertyName)] 21 | [string[]]$Name='', 22 | 23 | [Parameter(ValueFromPipelineByPropertyName)] 24 | [string]$Partition 25 | ) 26 | begin { 27 | #Test that the F5 session is in a valid format 28 | Test-F5Session($F5Session) 29 | 30 | Write-Verbose "NB: Node names are case-specific." 31 | } 32 | process { 33 | for($i=0; $i -lt $Name.Count -or $i -lt $Address.Count; $i++) { 34 | $itemname = Invoke-NullCoalescing {$Name[$i]} {''} 35 | $itemaddress = Invoke-NullCoalescing {$Address[$i]} {[PoshLTM.F5Address]::Any} 36 | $URI = $F5Session.BaseURL + 'node/{0}' -f (Get-ItemPath -Name $itemname -Partition $Partition) 37 | $JSON = Invoke-F5RestMethod -Method Get -Uri $URI -F5Session $F5Session -ErrorAction SilentlyContinue 38 | [bool]( 39 | Invoke-NullCoalescing {$JSON.items} {$JSON} | 40 | Where-Object { [PoshLTM.F5Address]::IsMatch($itemaddress, $_.address) } 41 | ) 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /F5-LTM/Public/Remove-VirtualServer.ps1: -------------------------------------------------------------------------------- 1 | Function Remove-VirtualServer{ 2 | <# 3 | .SYNOPSIS 4 | Remove the specified virtual server(s). Confirmation is needed. 5 | .NOTES 6 | Virtual server names are case-specific. 7 | #> 8 | [cmdletBinding( SupportsShouldProcess=$true, ConfirmImpact="High")] 9 | param ( 10 | $F5Session=$Script:F5Session, 11 | 12 | [Alias('VirtualServer')] 13 | [Parameter(Mandatory=$true,ParameterSetName='InputObject',ValueFromPipeline=$true)] 14 | [PSObject[]]$InputObject, 15 | 16 | [Alias('VirtualServerName')] 17 | [Parameter(Mandatory=$true,ParameterSetName='Name',ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] 18 | [string[]]$Name, 19 | 20 | [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] 21 | [string]$Partition 22 | ) 23 | begin { 24 | #Test that the F5 session is in a valid format 25 | Test-F5Session($F5Session) 26 | 27 | Write-Verbose "NB: Virtual server names are case-specific." 28 | } 29 | process { 30 | switch($PSCmdLet.ParameterSetName) { 31 | InputObject { 32 | foreach($virtualserver in $InputObject) { 33 | if ($pscmdlet.ShouldProcess($virtualserver.fullPath)){ 34 | $URI = $F5Session.GetLink($virtualserver.selfLink) 35 | #expandSubcollections is not a supported parameter for a DELETE call 36 | $URI = $URI -replace "expandSubcollections=true&", "" 37 | Invoke-F5RestMethod -Method DELETE -Uri $URI -F5Session $F5Session 38 | } 39 | } 40 | } 41 | Name { 42 | $Name | Get-VirtualServer -F5Session $F5Session -Partition $Partition | Remove-VirtualServer -F5session $F5Session 43 | } 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /F5-LTM/Public/Get-Node.ps1: -------------------------------------------------------------------------------- 1 | Function Get-Node { 2 | <# 3 | .SYNOPSIS 4 | Retrieve specified Node(s) 5 | .NOTES 6 | This function makes no attempt to resolve names to ip addresses. If you are having trouble finding a node, try: 7 | Get-Node | Where-Object { $_.address -like 'N.N.N.N' -or $_.name -like 'XXXXX' } 8 | #> 9 | [cmdletBinding()] 10 | param ( 11 | $F5Session=$Script:F5Session, 12 | 13 | [Parameter(ValueFromPipelineByPropertyName)] 14 | [PoshLTM.F5Address[]]$Address=[PoshLTM.F5Address]::Any, 15 | 16 | [Alias('ComputerName')] 17 | [Alias('NodeName')] 18 | [Parameter(ValueFromPipeline,ValueFromPipelineByPropertyName)] 19 | [string[]]$Name='', 20 | 21 | [Parameter(ValueFromPipelineByPropertyName)] 22 | [string]$Partition 23 | ) 24 | begin { 25 | # Test that the F5 session is in a valid format 26 | Test-F5Session($F5Session) 27 | 28 | Write-Verbose "NB: Node names are case-specific." 29 | } 30 | process { 31 | for($i=0; $i -lt $Name.Count -or $i -lt $Address.Count; $i++) { 32 | $itemname = Invoke-NullCoalescing {$Name[$i]} {''} 33 | $itemaddress = Invoke-NullCoalescing {$Address[$i]} {[PoshLTM.F5Address]::Any} 34 | $URI = $F5Session.BaseURL + 'node/{0}' -f (Get-ItemPath -Name $itemname -Partition $Partition) 35 | $JSON = Invoke-F5RestMethod -Method Get -Uri $URI -F5Session $F5Session 36 | # BIG-IP v 11.5 does not support FQDN nodes, and hence nodes require IP addresses and have no 'ephemeral' property 37 | Invoke-NullCoalescing {$JSON.items} {$JSON} | 38 | # Where-Object { $F5Session.LTMVersion.Major -eq '11' -or $_.ephemeral -eq 'false' } | 39 | Where-Object { [PoshLTM.F5Address]::IsMatch($itemaddress, $_.address) } | 40 | Add-ObjectDetail -TypeName 'PoshLTM.Node' 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /F5-LTM/Public/Enable-VirtualServer.ps1: -------------------------------------------------------------------------------- 1 | Function Enable-VirtualServer { 2 | <# 3 | .SYNOPSIS 4 | Enable a virtual server 5 | #> 6 | [cmdletBinding()] 7 | param( 8 | $F5Session=$Script:F5Session, 9 | 10 | [Alias('VirtualServer')] 11 | [Parameter(Mandatory=$false,ParameterSetName='InputObject',ValueFromPipeline=$true)] 12 | [PSObject[]]$InputObject, 13 | 14 | [Alias("VirtualServerName")] 15 | [Parameter(Mandatory=$false,ParameterSetName='VirtualServerName',ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] 16 | [string[]]$Name='', 17 | 18 | [Alias('iApp')] 19 | [Parameter(Mandatory=$false,ParameterSetName='VirtualServerName',ValueFromPipelineByPropertyName=$true)] 20 | [string]$Application, 21 | 22 | [Parameter(Mandatory=$false,ParameterSetName='VirtualServerName',ValueFromPipelineByPropertyName=$true)] 23 | [string]$Partition 24 | ) 25 | process { 26 | $JSONBody = "{`"enabled`":true}" 27 | switch($PSCmdLet.ParameterSetName) { 28 | InputObject { 29 | if ($null -eq $InputObject) { 30 | $InputObject = Get-VirtualServer -F5Session $F5Session -Partition $Partition 31 | } 32 | foreach($vs in $InputObject) { 33 | $URI = $F5Session.GetLink($vs.selfLink) 34 | $FullPath = $vs.fullPath 35 | $null = Invoke-F5RestMethod -Method PATCH -Uri $URI -F5Session $F5Session -Body $JSONBody 36 | Get-VirtualServer -F5Session $F5Session -Name $FullPath 37 | } 38 | } 39 | VirtualServerName { 40 | foreach ($itemname in $Name) { 41 | Get-VirtualServer -F5Session $F5Session -Name $Name -Application $Application -Partition $Partition | Enable-VirtualServer -F5session $F5Session 42 | } 43 | } 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /F5-LTM/Public/Disable-VirtualServer.ps1: -------------------------------------------------------------------------------- 1 | Function Disable-VirtualServer { 2 | <# 3 | .SYNOPSIS 4 | Disable a virtual server 5 | #> 6 | [cmdletBinding()] 7 | param( 8 | $F5Session=$Script:F5Session, 9 | 10 | [Alias('VirtualServer')] 11 | [Parameter(Mandatory=$false,ParameterSetName='InputObject',ValueFromPipeline=$true)] 12 | [PSObject[]]$InputObject, 13 | 14 | [Alias("VirtualServerName")] 15 | [Parameter(Mandatory=$false,ParameterSetName='VirtualServerName',ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] 16 | [string[]]$Name='', 17 | 18 | [Alias('iApp')] 19 | [Parameter(Mandatory=$false,ParameterSetName='VirtualServerName',ValueFromPipelineByPropertyName=$true)] 20 | [string]$Application, 21 | 22 | [Parameter(Mandatory=$false,ParameterSetName='VirtualServerName',ValueFromPipelineByPropertyName=$true)] 23 | [string]$Partition 24 | ) 25 | process { 26 | $JSONBody = "{`"disabled`":true}" 27 | switch($PSCmdLet.ParameterSetName) { 28 | InputObject { 29 | if ($null -eq $InputObject) { 30 | $InputObject = Get-VirtualServer -F5Session $F5Session -Partition $Partition 31 | } 32 | foreach($vs in $InputObject) { 33 | $URI = $F5Session.GetLink($vs.selfLink) 34 | $FullPath = $vs.fullPath 35 | $null = Invoke-F5RestMethod -Method PATCH -Uri $URI -F5Session $F5Session -Body $JSONBody 36 | Get-VirtualServer -F5Session $F5Session -Name $FullPath 37 | } 38 | } 39 | VirtualServerName { 40 | foreach ($itemname in $Name) { 41 | Get-VirtualServer -F5Session $F5Session -Name $Name -Application $Application -Partition $Partition | Disable-VirtualServer -F5session $F5Session 42 | } 43 | } 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /F5-LTM/Public/Remove-PoolMonitor.ps1: -------------------------------------------------------------------------------- 1 | Function Remove-PoolMonitor { 2 | <# 3 | .SYNOPSIS 4 | Remove health monitor(s) from a pool 5 | .NOTES 6 | Health monitor names are case-specific. 7 | #> 8 | [cmdletBinding( SupportsShouldProcess=$true, ConfirmImpact="Low")] 9 | param( 10 | $F5Session=$Script:F5Session, 11 | 12 | [Alias('Pool')] 13 | [Parameter(Mandatory=$true,ParameterSetName='InputObject',ValueFromPipeline=$true)] 14 | [PSObject[]]$InputObject, 15 | 16 | [Parameter(Mandatory=$true,ParameterSetName='PoolName',ValueFromPipelineByPropertyName=$true)] 17 | [string[]]$PoolName, 18 | [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] 19 | [string]$Partition, 20 | 21 | [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)] 22 | [string[]]$Name 23 | ) 24 | begin { 25 | #Test that the F5 session is in a valid format 26 | Test-F5Session($F5Session) 27 | 28 | Write-Verbose "NB: Health monitor names are case-specific." 29 | } 30 | process { 31 | switch($PSCmdLet.ParameterSetName) { 32 | InputObject { 33 | foreach($pool in $InputObject) { 34 | if ($pscmdlet.ShouldProcess($pool.fullPath)){ 35 | $monitor = ($pool.monitor -split ' and ' | Where-Object { $Name -notcontains $_.Trim() }) -join ' and ' 36 | $JSONBody = @{monitor=$monitor} | ConvertTo-Json 37 | $URI = $F5Session.GetLink($pool.selfLink) 38 | Invoke-F5RestMethod -Method PATCH -Uri "$URI" -F5Session $F5Session -Body $JSONBody -ContentType 'application/json' 39 | } 40 | } 41 | } 42 | PoolName { 43 | Get-Pool -F5session $F5Session -Name $PoolName -Partition $Partition | Remove-PoolMonitor -F5session $F5Session -Name $Name 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /F5-LTM/Private/Invoke-NullCoalescing.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Returns the first argument -ne $null, or $null if all arguments are $null. Similar to the C# ?? operator e.g. name = value ?? String.Empty 4 | .NOTES 5 | This was extracted from the PSCX module for internal use in this module without requiring a dependency on PSCX. 6 | .LINK 7 | https://github.com/flofreud/pscx/blob/master/about_Pscx.help.txt 8 | .LINK 9 | https://msdn.microsoft.com/en-us/library/ms173224.aspx 10 | .LINK 11 | https://msdn.microsoft.com/en-us/library/ms173224.aspx 12 | #> 13 | Function Invoke-NullCoalescing { 14 | param( 15 | [Parameter(Mandatory, Position=0)] 16 | [AllowNull()] 17 | [scriptblock] 18 | $PrimaryExpr, 19 | 20 | [Parameter(Mandatory, Position=1)] 21 | [scriptblock] 22 | $AlternateExpr, 23 | 24 | [Parameter(ValueFromPipeline, ParameterSetName='InputObject')] 25 | [psobject] 26 | $InputObject 27 | ) 28 | 29 | Process { 30 | if ($pscmdlet.ParameterSetName -eq 'InputObject') { 31 | if ($PrimaryExpr -eq $null) { 32 | Foreach-Object $AlternateExpr -InputObject $InputObject 33 | } 34 | else { 35 | $result = Foreach-Object $PrimaryExpr -input $InputObject 36 | if ($null -eq $result) { 37 | Foreach-Object $AlternateExpr -InputObject $InputObject 38 | } 39 | else { 40 | $result 41 | } 42 | } 43 | } 44 | else { 45 | if ($PrimaryExpr -eq $null) { 46 | &$AlternateExpr 47 | } 48 | else { 49 | $result = &$PrimaryExpr 50 | if ($null -eq $result) { 51 | &$AlternateExpr 52 | } 53 | else { 54 | $result 55 | } 56 | } 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /F5-LTM/F5-LTM.psm1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | A module for using the F5 LTM REST API to administer an LTM device 4 | .DESCRIPTION 5 | This module uses the F5 LTM REST API to manipulate and query pools, pool members, virtual servers and iRules 6 | It is built to work with version 11.6 and higher 7 | .NOTES 8 | File Name : F5-LTM.psm1 9 | Author : Joel Newton - jnewton@springcm.com 10 | Requires : PowerShell V3 11 | Dependencies : It includes a Validation.cs class file (based on code posted by Brian Scholer) to allow for using the REST API 12 | with LTM devices using self-signed SSL certificates. 13 | #> 14 | 15 | $Script:F5Session=$null 16 | 17 | if ($PSVersionTable.PSVersion.Major -lt 6) { 18 | Add-Type -Path "${PSScriptRoot}\Validation.cs" 19 | } 20 | 21 | #Only add this type if it isn't already added to the session 22 | if (!("PoshLTM.F5Address" -as [type])) { 23 | Add-Type -Path "${PSScriptRoot}\TypeData\PoshLTM.Types.cs" 24 | } 25 | Update-FormatData "${PSScriptRoot}\TypeData\PoshLTM.Format.ps1xml" 26 | $ScriptPath = Split-Path $MyInvocation.MyCommand.Path 27 | 28 | #Restrict .NET Framework to TLS v 1.2 29 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 30 | 31 | #region Load Public Functions 32 | 33 | Get-ChildItem "$ScriptPath\Public" -Filter *.ps1 -Recurse| Select-Object -Expand FullName | ForEach-Object { 34 | $Function = Split-Path $_ -Leaf 35 | try { 36 | . $_ 37 | } catch { 38 | Write-Warning ("{0}: {1}" -f $Function,$_.Exception.Message) 39 | } 40 | } 41 | 42 | #endregion 43 | 44 | #region Load Private Functions 45 | 46 | Get-ChildItem "$ScriptPath\Private" -Filter *.ps1 -Recurse | Select-Object -Expand FullName | ForEach-Object { 47 | $Function = Split-Path $_ -Leaf 48 | try { 49 | . $_ 50 | } catch { 51 | Write-Warning ("{0}: {1}" -f $Function,$_.Exception.Message) 52 | } 53 | } 54 | 55 | #endregion 56 | -------------------------------------------------------------------------------- /F5-LTM/Public/Get-PoolMonitor.ps1: -------------------------------------------------------------------------------- 1 | Function Get-PoolMonitor { 2 | <# 3 | .SYNOPSIS 4 | Get pool monitor(s) 5 | .NOTES 6 | Pool and monitor names are case-specific. 7 | #> 8 | [cmdletBinding(DefaultParameterSetName='InputObject')] 9 | param( 10 | $F5Session=$Script:F5Session, 11 | 12 | [Alias('Pool')] 13 | [Parameter(Mandatory=$false,ParameterSetName='InputObject',ValueFromPipeline=$true)] 14 | [PSObject[]]$InputObject, 15 | 16 | [Parameter(Mandatory=$true,ParameterSetName='PoolName',ValueFromPipeline=$true)] 17 | [string[]]$PoolName, 18 | 19 | [Parameter(Mandatory=$false)] 20 | [string]$Partition, 21 | 22 | [Parameter(Mandatory=$false)] 23 | [string[]]$Name='*' 24 | ) 25 | begin { 26 | #Test that the F5 session is in a valid format 27 | Test-F5Session($F5Session) 28 | 29 | Write-Verbose "NB: Pool and monitor names are case-specific." 30 | 31 | $monitors = @{} 32 | } 33 | process { 34 | switch($PSCmdLet.ParameterSetName) { 35 | InputObject { 36 | if ($null -eq $InputObject) { 37 | $InputObject = Get-Pool -F5Session $F5Session -Partition $Partition 38 | } 39 | ($InputObject | Select-Object -ExpandProperty monitor -ErrorAction SilentlyContinue) -split ' and ' | ForEach-Object { 40 | $monitorname = $_.Trim() 41 | if ($Name -eq '*' -or $Name -contains $monitorname) { 42 | $monitors[$monitorname]++ 43 | } 44 | } 45 | } 46 | PoolName { 47 | Get-Pool -F5Session $F5Session -Name $PoolName -Partition $Partition | Get-PoolMonitor -F5Session $F5Session -Name $Name 48 | } 49 | } 50 | } 51 | end { 52 | foreach ($key in $monitors.Keys) { 53 | [pscustomobject]@{Name=$key;Count=$monitors[$key]} 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /F5-LTM/Public/Remove-Node.ps1: -------------------------------------------------------------------------------- 1 | Function Remove-Node { 2 | <# 3 | .SYNOPSIS 4 | Remove the specified Node(s) 5 | .NOTES 6 | Node names are case-specific. 7 | #> 8 | [cmdletBinding( DefaultParameterSetName='Address', SupportsShouldProcess, ConfirmImpact='Medium')] 9 | param ( 10 | $F5Session=$Script:F5Session, 11 | 12 | [Alias('Node')] 13 | [Parameter(Mandatory,ParameterSetName='InputObject',ValueFromPipeline)] 14 | [PSObject[]]$InputObject, 15 | 16 | [Parameter(Mandatory,ParameterSetName='Address',ValueFromPipelineByPropertyName)] 17 | [Parameter(Mandatory,ParameterSetName='AddressAndName',ValueFromPipelineByPropertyName)] 18 | [PoshLTM.F5Address[]]$Address=[PoshLTM.F5Address]::Any, 19 | 20 | [Alias('ComputerName')] 21 | [Alias('NodeName')] 22 | [Parameter(Mandatory,ParameterSetName='Name',ValueFromPipeline,ValueFromPipelineByPropertyName)] 23 | [Parameter(Mandatory,ParameterSetName='AddressAndName',ValueFromPipeline,ValueFromPipelineByPropertyName)] 24 | [string[]]$Name='', 25 | 26 | [Parameter(ValueFromPipelineByPropertyName)] 27 | [string]$Partition 28 | ) 29 | begin { 30 | #Test that the F5 session is in a valid format 31 | Test-F5Session($F5Session) 32 | 33 | Write-Verbose "NB: Node names are case-specific." 34 | } 35 | process { 36 | switch($PSCmdLet.ParameterSetName) { 37 | InputObject { 38 | foreach($item in $InputObject) { 39 | if ($pscmdlet.ShouldProcess($item.fullPath)){ 40 | $URI = $F5Session.GetLink($item.selfLink) 41 | Invoke-F5RestMethod -Method DELETE -Uri $URI -F5Session $F5Session 42 | } 43 | } 44 | } 45 | default { 46 | Get-Node -F5Session $F5Session -Address $Address -Name $Name -Partition $Partition | Remove-Node -F5session $F5Session 47 | } 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /F5-LTM/Public/Remove-HealthMonitor.ps1: -------------------------------------------------------------------------------- 1 | Function Remove-HealthMonitor { 2 | <# 3 | .SYNOPSIS 4 | Remove the specified health monitor(s). Confirmation is needed. 5 | .NOTES 6 | Health monitor names are case-specific. 7 | #> 8 | [cmdletBinding( SupportsShouldProcess=$true, ConfirmImpact="Low")] 9 | param( 10 | $F5Session=$Script:F5Session, 11 | 12 | [Alias('HealthMonitor')] 13 | [Alias('Monitor')] 14 | [Parameter(Mandatory=$true,ParameterSetName='InputObject',ValueFromPipeline=$true)] 15 | [PSObject[]]$InputObject, 16 | 17 | [Alias('HealthMonitorName')] 18 | [Alias('MonitorName')] 19 | [Parameter(Mandatory=$true,ParameterSetName='Name',ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] 20 | [string[]]$Name, 21 | 22 | [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] 23 | [string[]]$Type, 24 | 25 | [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] 26 | [string]$Partition 27 | ) 28 | begin { 29 | #Test that the F5 session is in a valid format 30 | Test-F5Session($F5Session) 31 | 32 | Write-Verbose "NB: Health monitor names are case-specific." 33 | if ([string]::IsNullOrEmpty($Type)) { 34 | $Type = Get-HealthMonitorType -F5Session $F5Session 35 | } 36 | } 37 | process { 38 | switch($PSCmdLet.ParameterSetName) { 39 | InputObject { 40 | foreach($item in $InputObject) { 41 | if ($pscmdlet.ShouldProcess($item.fullPath)){ 42 | $URI = $F5Session.GetLink($item.selfLink) 43 | Invoke-F5RestMethod -Method DELETE -Uri $URI -F5Session $F5Session 44 | } 45 | } 46 | } 47 | Name { 48 | $Name | Get-HealthMonitor -F5Session $F5Session -Type $Type -Partition $Partition | Remove-HealthMonitor -F5session $F5Session 49 | } 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /F5-LTM/Private/Invoke-F5RestMethod.ps1: -------------------------------------------------------------------------------- 1 | Function Invoke-F5RestMethod { 2 | [cmdletBinding(DefaultParameterSetName='Anonymous')] 3 | [OutputType([Xml.XmlDocument])] 4 | [OutputType([Microsoft.PowerShell.Commands.BasicHtmlWebResponseObject])] 5 | [OutputType([String])] 6 | [OutputType([bool])] 7 | param ( 8 | [Parameter(Mandatory)][Microsoft.PowerShell.Commands.WebRequestMethod]$Method, 9 | [Parameter(Mandatory)][uri]$URI, 10 | [Parameter(Mandatory)]$F5Session, 11 | 12 | $Body, 13 | $Headers, 14 | $ContentType, 15 | $ErrorMessage, 16 | [switch]$AsBoolean 17 | ) 18 | 19 | try { 20 | # Remove params not understood by Invoke-RestMethod, so the remaining params can be splatted 21 | $null = $PSBoundParameters.Remove('AsBoolean') 22 | $null = $PSBoundParameters.Remove('F5Session') 23 | $null = $PSBoundParameters.Remove('ErrorMessage') 24 | if ($F5Session.WebSession.Headers.ContainsKey('X-F5-Auth-Token')) { 25 | $null = $PSBoundParameters.Add('WebSession', $F5Session.Websession) 26 | } else { 27 | $null = $PSBoundParameters.Add('Credential', $F5Session.Credential) 28 | } 29 | $Result = Invoke-RestMethodOverride @PSBoundParameters 30 | if ($AsBoolean) { 31 | $true 32 | } else { 33 | $Result 34 | } 35 | } catch { 36 | if ($AsBoolean) { 37 | $false 38 | } else { 39 | #Try to treat the returned error as JSON. If it isn't, then treat it as a string 40 | try { 41 | $message = $_.ErrorDetails.Message | ConvertFrom-json | Select-Object -expandproperty message 42 | } catch { 43 | $message = [string]$_.ErrorDetails.Message 44 | } 45 | $ErrorOutput = '"{0} {1}: {2}' -f $_.Exception.Response.StatusCode.value__,$_.Exception.Response.StatusDescription,(Invoke-NullCoalescing {$message} {$ErrorMessage}) 46 | Write-Error $ErrorOutput 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /F5-LTM/Public/Enable-Node.ps1: -------------------------------------------------------------------------------- 1 | Function Enable-Node { 2 | <# 3 | .SYNOPSIS 4 | Enable a node to quickly enable all pool members associated with it 5 | #> 6 | [cmdletBinding(DefaultParameterSetName='Address')] 7 | param ( 8 | $F5Session=$Script:F5Session, 9 | 10 | [Alias('Node')] 11 | [Parameter(Mandatory,ParameterSetName='InputObject',ValueFromPipeline)] 12 | [PSObject[]]$InputObject, 13 | 14 | [Parameter(Mandatory,ParameterSetName='Address',ValueFromPipelineByPropertyName)] 15 | [Parameter(Mandatory,ParameterSetName='AddressAndName',ValueFromPipelineByPropertyName)] 16 | [PoshLTM.F5Address[]]$Address=[PoshLTM.F5Address]::Any, 17 | 18 | [Alias('ComputerName')] 19 | [Alias('NodeName')] 20 | [Parameter(Mandatory,ParameterSetName='Name',ValueFromPipeline,ValueFromPipelineByPropertyName)] 21 | [Parameter(Mandatory,ParameterSetName='AddressAndName',ValueFromPipeline,ValueFromPipelineByPropertyName)] 22 | [string[]]$Name='', 23 | 24 | [Parameter(ValueFromPipelineByPropertyName)] 25 | [string]$Partition 26 | ) 27 | begin { 28 | #If the -Force param is specified pool members do not accept any new connections, even if they match an existing persistence session. 29 | #Otherwise, members will only accept new connections that match an existing persistence session. 30 | } 31 | process { 32 | switch($PSCmdLet.ParameterSetName) { 33 | InputObject { 34 | $JSONBody = @{state='user-up';session='user-enabled'} | ConvertTo-Json 35 | foreach($member in $InputObject) { 36 | $URI = $F5Session.GetLink($member.selfLink) 37 | Invoke-F5RestMethod -Method PATCH -Uri "$URI" -F5Session $F5Session -Body $JSONBody -ErrorMessage "Failed to enable node $($member.Name)." -AsBoolean 38 | } 39 | } 40 | default { 41 | Get-Node -F5session $F5Session -Address $Address -Name $Name -Partition $Partition | Enable-Node -F5session $F5Session 42 | } 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /F5-LTM/Public/Get-PoolsForMember.ps1: -------------------------------------------------------------------------------- 1 | Function Get-PoolsForMember { 2 | <# 3 | .SYNOPSIS 4 | Determine which pool(s) a pool member is in. Expects either a pool member object, an IP address, or a pool member name to be passed as a parameter 5 | .EXAMPLE 6 | #Get all pools that 'member1' pool member is in 7 | Get-poolmember -Name 'member1' | Get-PoolsForMember 8 | 9 | #> 10 | [cmdletBinding()] 11 | param( 12 | $F5Session=$Script:F5Session, 13 | [Parameter(Mandatory=$true,ParameterSetName='InputObject',ValueFromPipeline=$true)] 14 | [Alias('PoolMember')] 15 | [PSObject]$InputObject, 16 | 17 | [Parameter(Mandatory=$false,ParameterSetName='Address')] 18 | [PoshLTM.F5Address[]]$Address=[PoshLTM.F5Address]::Any, 19 | 20 | [Parameter(Mandatory=$false,ParameterSetName='Name')] 21 | [String]$Name 22 | ) 23 | begin { 24 | #Test that the F5 session is in a valid format 25 | Test-F5Session($F5Session) 26 | } 27 | process { 28 | switch($PSCmdLet.ParameterSetName) { 29 | Address { 30 | $pools = Get-Pool -F5Session $F5Session 31 | foreach ($pool in $pools) { 32 | $members = $pool | Get-PoolMember -F5session $F5Session | Where-Object { [PoshLTM.F5Address]::IsMatch($Address, $_.address) } 33 | if ($members) { 34 | $pool 35 | } 36 | } 37 | } 38 | Name { 39 | $pools = Get-Pool -F5Session $F5Session 40 | foreach ($pool in $pools) { 41 | $members = $pool | Get-PoolMember -F5session $F5Session | Where-Object { $_.name -eq $Name } 42 | if ($members) { 43 | $pool 44 | } 45 | } 46 | } 47 | InputObject { 48 | foreach($member in $InputObject) { 49 | Get-PoolsForMember -F5Session $F5Session -Address $member.address 50 | } 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /F5-LTM/Public/Get-HealthMonitor.ps1: -------------------------------------------------------------------------------- 1 | Function Get-HealthMonitor { 2 | <# 3 | .SYNOPSIS 4 | Retrieve specified health monitor(s) 5 | .NOTES 6 | Health monitor names are case-specific. 7 | #> 8 | [cmdletBinding()] 9 | param ( 10 | $F5Session=$Script:F5Session, 11 | 12 | [Alias('MonitorName')] 13 | [Parameter(Mandatory=$false,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] 14 | [string[]]$Name='', 15 | 16 | [Alias('iApp')] 17 | [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] 18 | [string]$Application='', 19 | 20 | [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] 21 | [string]$Partition, 22 | 23 | [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] 24 | [string[]]$Type 25 | ) 26 | begin { 27 | #Test that the F5 session is in a valid format 28 | Test-F5Session($F5Session) 29 | 30 | Write-Verbose "NB: Health monitor names are case-specific." 31 | $TypeSearchErrorAction = $ErrorActionPreference 32 | if ([string]::IsNullOrEmpty($Type)) { 33 | $TypeSearchErrorAction = 'SilentlyContinue' 34 | $Type = Get-HealthMonitorType -F5Session $F5Session 35 | } 36 | } 37 | process { 38 | foreach ($typename in $Type) { 39 | foreach ($itemname in $Name) { 40 | $URI = $F5Session.BaseURL + 'monitor/{0}' -f ($typename,(Get-ItemPath -Name $itemname -Application $Application -Partition $Partition) -join '/') 41 | $JSON = Invoke-F5RestMethod -Method Get -Uri $URI -F5Session $F5Session -ErrorAction $TypeSearchErrorAction 42 | if ($JSON.items -or $JSON.name) { 43 | $items = Invoke-NullCoalescing {$JSON.items} {$JSON} 44 | if(![string]::IsNullOrWhiteSpace($Application) -and ![string]::IsNullOrWhiteSpace($Partition)) { 45 | $items = $items | Where-Object {$_.fullPath -like "/$Partition/$Application.app/*"} 46 | } 47 | $items | Add-ObjectDetail -TypeName 'PoshLTM.HealthMonitor' -PropertyToAdd @{type=$typename} 48 | } 49 | } 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /F5-LTM/Public/Enable-PoolMember.ps1: -------------------------------------------------------------------------------- 1 | Function Enable-PoolMember { 2 | <# 3 | .SYNOPSIS 4 | Enable a pool member in the specified pools 5 | If no pool is specified, the member will be enabled in all pools, provided that the input consists of pool member objects, and not address(es) or name(s). 6 | #> 7 | [cmdletBinding()] 8 | param( 9 | $F5Session=$Script:F5Session, 10 | 11 | [Parameter(Mandatory=$true,ParameterSetName='InputObject',ValueFromPipeline=$true)] 12 | [Alias('PoolMember')] 13 | [PSObject[]]$InputObject, 14 | 15 | [Parameter(Mandatory=$true,ParameterSetName='PoolName',ValueFromPipeline=$true)] 16 | [string[]]$PoolName, 17 | [Parameter(Mandatory=$false,ParameterSetName='PoolName')] 18 | [string]$Partition, 19 | 20 | [PoshLTM.F5Address]$Address=[PoshLTM.F5Address]::Any, 21 | 22 | [string]$Name='*' 23 | ) 24 | process { 25 | switch($PSCmdLet.ParameterSetName) { 26 | InputObject { 27 | switch ($InputObject.kind) { 28 | "tm:ltm:pool:poolstate" { 29 | if ($Address -eq [PoshLTM.F5Address]::Any) { 30 | Write-Error 'Address is required when the pipeline object is not a PoolMember' 31 | } else { 32 | $InputObject | Get-PoolMember -F5session $F5Session -Address $Address -Name $Name | Enable-PoolMember -F5session $F5Session 33 | } 34 | } 35 | "tm:ltm:pool:members:membersstate" { 36 | $JSONBody = @{state='user-up';session='user-enabled'} | ConvertTo-Json 37 | foreach($member in $InputObject) { 38 | $URI = $F5Session.GetLink($member.selfLink) 39 | Invoke-F5RestMethod -Method PATCH -Uri "$URI" -F5Session $F5Session -Body $JSONBody -ErrorMessage "Failed to enable $Address in the $PoolName pool." -AsBoolean 40 | } 41 | } 42 | } 43 | } 44 | PoolName { 45 | Get-PoolMember -F5session $F5Session -PoolName $PoolName -Partition $Partition -Address $Address -Name $Name | Enable-PoolMember -F5session $F5Session 46 | } 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /F5-LTM/Public/Disable-Node.ps1: -------------------------------------------------------------------------------- 1 | Function Disable-Node { 2 | <# 3 | .SYNOPSIS 4 | Disable a node to quickly disable all pool members associated with it 5 | #> 6 | [cmdletBinding(DefaultParameterSetName='Address')] 7 | param ( 8 | $F5Session=$Script:F5Session, 9 | 10 | [Alias('Node')] 11 | [Parameter(Mandatory,ParameterSetName='InputObject',ValueFromPipeline)] 12 | [PSObject[]]$InputObject, 13 | 14 | [Parameter(Mandatory,ParameterSetName='Address',ValueFromPipelineByPropertyName)] 15 | [Parameter(Mandatory,ParameterSetName='AddressAndName',ValueFromPipelineByPropertyName)] 16 | [PoshLTM.F5Address[]]$Address=[PoshLTM.F5Address]::Any, 17 | 18 | [Alias('ComputerName')] 19 | [Alias('NodeName')] 20 | [Parameter(Mandatory,ParameterSetName='Name',ValueFromPipeline,ValueFromPipelineByPropertyName)] 21 | [Parameter(Mandatory,ParameterSetName='AddressAndName',ValueFromPipeline,ValueFromPipelineByPropertyName)] 22 | [string[]]$Name='', 23 | 24 | [Parameter(ValueFromPipelineByPropertyName)] 25 | [string]$Partition, 26 | 27 | [switch]$Force 28 | ) 29 | begin { 30 | #If the -Force param is specified pool members do not accept any new connections, even if they match an existing persistence session. 31 | #Otherwise, members will only accept new connections that match an existing persistence session. 32 | } 33 | process { 34 | switch($PSCmdLet.ParameterSetName) { 35 | InputObject { 36 | If ($Force){ 37 | $AcceptNewConnections = "user-down" 38 | } 39 | Else { 40 | $AcceptNewConnections = "user-up" 41 | } 42 | $JSONBody = @{state=$AcceptNewConnections;session='user-disabled'} | ConvertTo-Json 43 | foreach($member in $InputObject) { 44 | $URI = $F5Session.GetLink($member.selfLink) 45 | Invoke-F5RestMethod -Method PATCH -Uri "$URI" -F5Session $F5Session -Body $JSONBody -ErrorMessage "Failed to disable node $($member.Name)." -AsBoolean 46 | } 47 | } 48 | default { 49 | Get-Node -F5session $F5Session -Address $Address -Name $Name -Partition $Partition | Disable-Node -F5session $F5Session -Force:$Force 50 | } 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /F5-LTM/Public/Set-PoolMemberRatio.ps1: -------------------------------------------------------------------------------- 1 | Function Set-PoolMemberRatio { 2 | <# 3 | .SYNOPSIS 4 | Set the ratio value for the specified pool member 5 | #> 6 | [cmdletbinding()] 7 | param( 8 | $F5Session=$Script:F5Session, 9 | 10 | [Parameter(Mandatory=$true,ParameterSetName='InputObject',ValueFromPipeline=$true)] 11 | [Alias('PoolMember')] 12 | [PSObject[]]$InputObject, 13 | 14 | [Parameter(Mandatory=$true,ParameterSetName='PoolName',ValueFromPipeline=$true)] 15 | [string[]]$PoolName, 16 | [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] 17 | [string]$Partition, 18 | 19 | [Parameter(Mandatory=$false,ParameterSetName='InputObject')] 20 | [Parameter(Mandatory=$true,ParameterSetName='PoolName')] 21 | [PoshLTM.F5Address]$Address=[PoshLTM.F5Address]::Any, 22 | 23 | [string]$Name='*', 24 | 25 | [Parameter(Mandatory=$true)]$Ratio 26 | ) 27 | process { 28 | switch($PSCmdLet.ParameterSetName) { 29 | InputObject { 30 | switch ($InputObject.kind) { 31 | "tm:ltm:pool:poolstate" { 32 | if ($Address -eq [PoshLTM.F5Address]::Any) { 33 | Write-Error 'Address is required when the pipeline object is not a PoolMember' 34 | } else { 35 | $InputObject | Get-PoolMember -F5session $F5Session -Address $Address -Name $Name | Set-PoolMemberRatio -F5session $F5Session 36 | } 37 | } 38 | "tm:ltm:pool:members:membersstate" { 39 | foreach($member in $InputObject) { 40 | $JSONBody = @{ratio=$Ratio} | ConvertTo-Json 41 | $URI = $F5Session.GetLink($member.selfLink) 42 | Invoke-F5RestMethod -Method PATCH -Uri "$URI" -F5Session $F5Session -Body $JSONBody -ContentType 'application/json' -ErrorMessage "Failed to set the ratio on $($member.Name) in the $PoolName pool to $Ratio." 43 | } 44 | } 45 | } 46 | } 47 | PoolName { 48 | Get-PoolMember -F5session $F5Session -PoolName $PoolName -Partition $Partition -Address $Address -Name $Name | Set-PoolMemberRatio -F5session $F5Session -Ratio $Ratio 49 | } 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /F5-LTM/Public/Set-PoolMemberDescription.ps1: -------------------------------------------------------------------------------- 1 | Function Set-PoolMemberDescription { 2 | <# 3 | .SYNOPSIS 4 | Set the description value for the specified pool member 5 | #> 6 | [cmdletbinding()] 7 | param( 8 | $F5Session=$Script:F5Session, 9 | 10 | [Parameter(Mandatory=$true,ParameterSetName='InputObject',ValueFromPipeline=$true)] 11 | [Alias('PoolMember')] 12 | [PSObject[]]$InputObject, 13 | 14 | [Parameter(Mandatory=$true,ParameterSetName='PoolName',ValueFromPipeline=$true)] 15 | [string[]]$PoolName, 16 | [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] 17 | [string]$Partition, 18 | 19 | [Parameter(Mandatory=$false,ParameterSetName='InputObject')] 20 | [Parameter(Mandatory=$true,ParameterSetName='PoolName')] 21 | [PoshLTM.F5Address]$Address=[PoshLTM.F5Address]::Any, 22 | 23 | [string]$Name='*', 24 | 25 | [Parameter(Mandatory=$true)]$Description 26 | ) 27 | process { 28 | switch($PSCmdLet.ParameterSetName) { 29 | InputObject { 30 | switch ($InputObject.kind) { 31 | "tm:ltm:pool:poolstate" { 32 | if ($Address -eq [PoshLTM.F5Address]::Any) { 33 | Write-Error 'Address is required when the pipeline object is not a PoolMember' 34 | } else { 35 | $InputObject | Get-PoolMember -F5session $F5Session -Address $Address -Name $Name | Set-PoolMemberDescription -F5session $F5Session 36 | } 37 | } 38 | "tm:ltm:pool:members:membersstate" { 39 | foreach($member in $InputObject) { 40 | $JSONBody = @{description=$Description} | ConvertTo-Json 41 | $URI = $F5Session.GetLink($member.selfLink) 42 | Invoke-F5RestMethod -Method PATCH -Uri "$URI" -F5Session $F5Session -Body $JSONBody -ContentType 'application/json' -ErrorMessage "Failed to set the description on $($member.Name) in the $PoolName pool to $Description." -AsBoolean 43 | } 44 | } 45 | } 46 | } 47 | PoolName { 48 | Get-PoolMember -F5session $F5Session -PoolName $PoolName -Partition $Partition -Address $Address -Name $Name | Set-PoolMemberDescription -F5session $F5Session -Description $Description 49 | } 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # Set to FALSE because it is not a .NET project 2 | build: false 3 | 4 | version: 1.4.{build} 5 | os: WMF 5 6 | 7 | branches: 8 | only: 9 | - master 10 | 11 | environment: 12 | # Encrypted PowerShellGallery API Key to facilitate publishing 13 | # See https://ci.appveyor.com/tools/encrypt 14 | POWERSHELLGALLERY_APIKEY: 15 | secure: 0iQm4sa3PCnOM5hb+mqVmw3Etc+8KSp6MB/LrO1RaIO+c2MeG8bvY34vFYmbMxNE 16 | 17 | skip_commits: 18 | # Skip on updates to the readme. [skip ci] or [ci skip] anywhere in commit message will also prevent a ci build 19 | message: /update readme.*/ 20 | 21 | install: 22 | # Force bootstrap of the Nuget PackageManagement Provider; Reference: http://www.powershellgallery.com/GettingStarted?section=Get%20Started 23 | - ps: Get-PackageProvider -Name NuGet -Force 24 | # Install pester PowerShell Unit Testing module 25 | - cinst -y pester 26 | 27 | before_test: 28 | # Set FunctionsToExport and ModuleVersion in the module manifest (F5-LTM.psd1); Fixes #37 Do not export Private\*.ps1 functions 29 | - ps: | 30 | $FunctionsToExport = ((Get-ChildItem (Join-Path $env:APPVEYOR_BUILD_FOLDER 'F5-LTM\Public') -Filter '*.ps1' -Recurse).BaseName) -join "','" 31 | (Get-Content (Join-Path $env:APPVEYOR_BUILD_FOLDER 'F5-LTM\F5-LTM.psd1') -Raw) -replace 32 | "FunctionsToExport = '.*'","FunctionsToExport = '$FunctionsToExport'" -replace 33 | "ModuleVersion = '.*'", "ModuleVersion = '$env:APPVEYOR_BUILD_VERSION'" | 34 | Set-Content (Join-Path $env:APPVEYOR_BUILD_FOLDER 'F5-LTM\F5-LTM.psd1') 35 | 36 | test_script: 37 | # Install PSScriptAnalyzer module for enforcing best practices 38 | - ps: Install-Module -Name PSScriptAnalyzer -Force 39 | # Invoke PSScriptAnalyzer against the module to make sure it's not failing any tests 40 | - ps: Invoke-ScriptAnalyzer -Path (Join-Path $env:APPVEYOR_BUILD_FOLDER 'F5-LTM') -Recurse 41 | # Invoke-Pester unit tests 42 | - ps: $testResultsFile = '.\TestsResults.xml' 43 | # JN: The Pester tests have failed for years because the Test-F5Sesssion does not recognize the mocked session object that is passed. 44 | # I do not know Pester. If someone else wants to fix the Pester tests and submit a PR, please do so. 45 | # Until them, they will remain commented out. 46 | # run tests 47 | # - ps: $res = Invoke-Pester -OutputFormat NUnitXml -OutputFile $testResultsFile -PassThru 48 | # upload results to AppVeyor 49 | # - ps: (New-Object 'System.Net.WebClient').UploadFile("https://ci.appveyor.com/api/testresults/nunit/$($env:APPVEYOR_JOB_ID)", (Resolve-Path $testResultsFile)) 50 | # if failures, quit to prevent publish 51 | # - ps: if ($res.FailedCount -gt 0) { "$($res.FailedCount) tests failed." } 52 | 53 | deploy_script: 54 | # Publish module to the PowerShellGallery 55 | - ps: Publish-Module -NugetApiKey $env:POWERSHELLGALLERY_APIKEY -Path (Join-Path $env:APPVEYOR_BUILD_FOLDER 'F5-LTM') 56 | -------------------------------------------------------------------------------- /F5-LTM/Public/Get-PoolMember.ps1: -------------------------------------------------------------------------------- 1 | Function Get-PoolMember { 2 | <# 3 | .SYNOPSIS 4 | Retrieve specified pool member(s) 5 | .NOTES 6 | Pool and member names are case-specific. 7 | #> 8 | [cmdletBinding(DefaultParameterSetName='InputObject')] 9 | param( 10 | $F5Session=$Script:F5Session, 11 | 12 | [Alias('Pool')] 13 | [Parameter(Mandatory=$false,ParameterSetName='InputObject',ValueFromPipeline=$true)] 14 | [PSObject[]]$InputObject, 15 | 16 | [Parameter(Mandatory=$false,ParameterSetName='PoolName',ValueFromPipeline=$true)] 17 | [string[]]$PoolName='', 18 | 19 | [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] 20 | [string]$Partition, 21 | 22 | [Parameter(Mandatory=$false)] 23 | [PoshLTM.F5Address[]]$Address=[PoshLTM.F5Address]::Any, 24 | 25 | [Parameter(Mandatory=$false)] 26 | [string[]]$Name='*', 27 | 28 | [Alias('iApp')] 29 | [Parameter(Mandatory=$false)] 30 | [string]$Application='' 31 | ) 32 | begin { 33 | #Test that the F5 session is in a valid format 34 | Test-F5Session($F5Session) 35 | 36 | Write-Verbose "NB: Pool and member names are case-specific." 37 | } 38 | process { 39 | switch($PSCmdLet.ParameterSetName) { 40 | InputObject { 41 | if ($null -eq $InputObject) { 42 | $InputObject = Get-Pool -F5Session $F5Session -Partition $Partition -Application $Application 43 | } 44 | foreach($pool in $InputObject) { 45 | $MembersLink = $F5Session.GetLink($pool.membersReference.link) 46 | $JSON = Invoke-F5RestMethod -Method Get -Uri $MembersLink -F5Session $F5Session 47 | #BIG-IP v 11.5 does not support FQDN nodes, and hence nodes require IP addresses and have no 'ephemeral' property 48 | Invoke-NullCoalescing {$JSON.items} {$JSON} | Where-Object { ($F5Session.LTMVersion.Major -eq '11' -or $_.ephemeral -eq 'false') -and [PoshLTM.F5Address]::IsMatch($Address, $_.address) -and ($Name -eq '*' -or $Name -contains $_.name) } | Add-Member -Name GetPoolName -MemberType ScriptMethod { 49 | [Regex]::Match($this.selfLink, '(?<=pool/)[^/]*') -replace '~','/' 50 | } -Force -PassThru | Add-Member -Name GetFullName -MemberType ScriptMethod { 51 | '{0}{1}' -f $this.GetPoolName(),$this.fullPath 52 | } -Force -PassThru | Add-ObjectDetail -TypeName 'PoshLTM.PoolMember' 53 | } 54 | } 55 | PoolName { 56 | Get-Pool -F5Session $F5Session -Name $PoolName -Partition $Partition -Application $Application | Get-PoolMember -F5session $F5Session -Address $Address -Name $Name -Application $Application | Sort-object Name | Get-Unique –AsString 57 | } 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /F5-LTM/Public/Disable-PoolMember.ps1: -------------------------------------------------------------------------------- 1 | Function Disable-PoolMember { 2 | <# 3 | .SYNOPSIS 4 | Disable a pool member in the specified pools 5 | If no pool is specified, the member will be disabled in all pools, provided that the input consists of pool member objects, and not address(es) or name(s). 6 | #> 7 | [cmdletBinding()] 8 | param( 9 | $F5Session=$Script:F5Session, 10 | 11 | [Parameter(Mandatory=$true,ParameterSetName='InputObject',ValueFromPipeline=$true)] 12 | [Alias('PoolMember')] 13 | [PSObject[]]$InputObject, 14 | 15 | [Parameter(Mandatory=$true,ParameterSetName='PoolName',ValueFromPipeline=$true)] 16 | [string[]]$PoolName, 17 | [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] 18 | [string]$Partition, 19 | 20 | [PoshLTM.F5Address]$Address=[PoshLTM.F5Address]::Any, 21 | 22 | [Parameter(Mandatory=$false)] 23 | [string]$Name='*', 24 | 25 | [switch]$Force 26 | ) 27 | begin { 28 | #If the -Force param is specified pool members do not accept any new connections, even if they match an existing persistence session. 29 | #Otherwise, members will only accept new connections that match an existing persistence session. 30 | } 31 | process { 32 | switch($PSCmdLet.ParameterSetName) { 33 | InputObject { 34 | switch ($InputObject.kind) { 35 | "tm:ltm:pool:poolstate" { 36 | if ($Address -eq [PoshLTM.F5Address]::Any) { 37 | Write-Error 'Address is required when the pipeline object is not a PoolMember' 38 | } else { 39 | $InputObject | Get-PoolMember -F5session $F5Session -Address $Address -Name $Name | Disable-PoolMember -F5session $F5Session -Force:$Force 40 | } 41 | } 42 | "tm:ltm:pool:members:membersstate" { 43 | If ($Force){ 44 | $AcceptNewConnections = "user-down" 45 | } 46 | Else { 47 | $AcceptNewConnections = "user-up" 48 | } 49 | $JSONBody = @{state=$AcceptNewConnections;session='user-disabled'} | ConvertTo-Json 50 | foreach($member in $InputObject) { 51 | $URI = $F5Session.GetLink($member.selfLink) 52 | Invoke-F5RestMethod -Method PATCH -Uri "$URI" -F5Session $F5Session -Body $JSONBody -ErrorMessage "Failed to disable $Address in the $PoolName pool." -AsBoolean 53 | } 54 | } 55 | } 56 | } 57 | PoolName { 58 | Get-PoolMember -F5session $F5Session -PoolName $PoolName -Partition $Partition -Address $Address -Name $Name | Disable-PoolMember -F5session $F5Session -Force:$Force 59 | } 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /F5-LTM/Public/Remove-PoolMember.ps1: -------------------------------------------------------------------------------- 1 | Function Remove-PoolMember { 2 | <# 3 | .SYNOPSIS 4 | Remove member node(s) from a pool 5 | .NOTES 6 | Pool and member names are case-specific. 7 | #> 8 | [cmdletBinding( SupportsShouldProcess=$true, ConfirmImpact='High')] 9 | param ( 10 | $F5Session=$Script:F5Session, 11 | 12 | # InputObject could be Pool objects, but should ultimately be PoolMember objects 13 | [Alias('Pool')] 14 | [Alias('PoolMember')] 15 | [Parameter(Mandatory=$true,ParameterSetName='InputObject',ValueFromPipeline=$true)] 16 | [PSObject[]]$InputObject, 17 | 18 | [Parameter(Mandatory=$true,ParameterSetName='PoolName',ValueFromPipeline=$true)] 19 | [string[]]$PoolName, 20 | 21 | [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] 22 | [string]$Partition, 23 | 24 | [Parameter(Mandatory=$false)] 25 | [PoshLTM.F5Address[]]$Address=[PoshLTM.F5Address]::Any, 26 | 27 | [Parameter(Mandatory=$false)] 28 | [string[]]$Name='*', 29 | 30 | [Alias('iApp')] 31 | [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] 32 | [string]$Application='' 33 | ) 34 | begin { 35 | #Test that the F5 session is in a valid format 36 | Test-F5Session($F5Session) 37 | } 38 | process { 39 | switch($PSCmdLet.ParameterSetName) { 40 | InputObject { 41 | foreach($item in $InputObject) { 42 | switch ($item.kind) { 43 | "tm:ltm:pool:poolstate" { 44 | if ($Address -ne [PoshLTM.F5Address]::Any -or $Name) { 45 | $InputObject | Get-PoolMember -F5session $F5Session -Address $Address -Name $Name -Application $Application | Remove-PoolMember -F5session $F5Session 46 | } else { 47 | Write-Error 'Address and/or Name is required when the pipeline object is not a PoolMember' 48 | } 49 | } 50 | "tm:ltm:pool:members:membersstate" { 51 | if ($pscmdlet.ShouldProcess($item.GetFullName())) { 52 | $URI = $F5Session.GetLink($item.selfLink) 53 | Invoke-F5RestMethod -Method DELETE -Uri $URI -F5Session $F5Session 54 | } 55 | } 56 | } 57 | } 58 | } 59 | PoolName { 60 | $PoolMember = Get-PoolMember -F5session $F5Session -PoolName $PoolName -Partition $Partition -Address $Address -Name $Name -Application $Application; 61 | If ($PoolMember) { 62 | $PoolMember | Remove-PoolMember -F5session $F5Session 63 | } 64 | Else { 65 | Write-Warning "Pool member not found" 66 | } 67 | } 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /F5-LTM/Public/Get-PoolMemberStats.ps1: -------------------------------------------------------------------------------- 1 | Function Get-PoolMemberStats { 2 | <# 3 | .SYNOPSIS 4 | Retrieve pool member statistics 5 | .NOTES 6 | Pool and member names are case-specific. 7 | #> 8 | [cmdletBinding()] 9 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "")] 10 | param ( 11 | $F5Session=$Script:F5Session, 12 | 13 | # InputObject could be Pool objects, but should ultimately be PoolMember objects 14 | [Alias('Pool')] 15 | [Alias('PoolMember')] 16 | [Parameter(Mandatory=$true,ParameterSetName='InputObject',ValueFromPipeline=$true)] 17 | [PSObject[]]$InputObject, 18 | 19 | [Parameter(Mandatory=$true,ParameterSetName='PoolName',ValueFromPipeline=$true)] 20 | [string[]]$PoolName, 21 | 22 | [Parameter(Mandatory=$false)] 23 | [string]$Partition, 24 | 25 | [Parameter(Mandatory=$false)] 26 | [PoshLTM.F5Address[]]$Address=[PoshLTM.F5Address]::Any, 27 | 28 | [Parameter(Mandatory=$false)] 29 | [string[]]$Name='*', 30 | 31 | [Alias('iApp')] 32 | [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] 33 | [string]$Application='' 34 | ) 35 | begin { 36 | #Test that the F5 session is in a valid format 37 | Test-F5Session($F5Session) 38 | } 39 | process { 40 | switch($PSCmdLet.ParameterSetName) { 41 | InputObject { 42 | foreach($item in $InputObject) { 43 | switch ($item.kind) { 44 | "tm:ltm:pool:poolstate" { 45 | if ($Address -ne [PoshLTM.F5Address]::Any -or $Name) { 46 | $InputObject | Get-PoolMember -F5session $F5Session -Address $Address -Name $Name -Application $Application | Get-PoolMemberStats -F5session $F5Session 47 | } else { 48 | Write-Error 'Address and/or Name is required when the pipeline object is not a PoolMember' 49 | } 50 | } 51 | "tm:ltm:pool:members:membersstate" { 52 | $URI = $F5Session.GetLink(($item.selfLink -replace '\?','/stats?')) 53 | $JSON = Invoke-F5RestMethod -Method Get -Uri $URI -F5Session $F5Session 54 | 55 | $JSON = Resolve-NestedStats -F5Session $F5Session -JSONData $JSON 56 | 57 | Invoke-NullCoalescing {$JSON.entries} {$JSON} #| 58 | # Add-ObjectDetail -TypeName 'PoshLTM.PoolMemberStats' 59 | # TODO: Establish a type for formatting and return more columns, and consider adding a GetStats() ScriptMethod to PoshLTM.PoolMember 60 | } 61 | } 62 | } 63 | } 64 | PoolName { 65 | Get-PoolMember -F5session $F5Session -PoolName $PoolName -Partition $Partition -Address $Address -Name $Name -Application $Application | Get-PoolMemberStats -F5Session $F5Session 66 | } 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /F5-LTM/Public/Add-iRuleToVirtualServer.ps1: -------------------------------------------------------------------------------- 1 | Function Add-iRuleToVirtualServer { 2 | <# 3 | .SYNOPSIS 4 | Add an iRule to the specified virtual server 5 | .NOTES 6 | This function defaults to the /Common partition 7 | #> 8 | [cmdletbinding()] 9 | param( 10 | $F5Session=$Script:F5Session, 11 | 12 | [Parameter(Mandatory=$true,ParameterSetName='InputObject',ValueFromPipeline=$true)] 13 | [PSObject[]]$InputObject, 14 | 15 | [Alias("VirtualServer")] 16 | [Alias("VirtualServerName")] 17 | [Parameter(Mandatory=$true,ParameterSetName='Name',ValueFromPipeline=$true)] 18 | [string[]]$Name, 19 | 20 | [Parameter(Mandatory=$true)] 21 | [string]$iRuleName, 22 | 23 | [Parameter(Mandatory=$false)] 24 | [string]$Partition='Common' 25 | ) 26 | begin { 27 | #Test that the F5 session is in a valid format 28 | Test-F5Session($F5Session) 29 | } 30 | process { 31 | switch($PSCmdLet.ParameterSetName) { 32 | InputObject { 33 | #Verify that the iRule exists on the F5 LTM 34 | $iRule = Get-iRule -F5session $F5Session -Name $iRuleName -Partition $Partition 35 | If ($null -eq $iRule){ 36 | Write-Error "The $iRuleName iRule does not exist in this F5 LTM." 37 | $false 38 | } else { 39 | $iRuleFullName = $iRule.fullPath 40 | foreach($virtualserver in $InputObject) { 41 | #Get the existing IRules on the virtual server 42 | [array]$iRules = $virtualserver | Select-Object -ExpandProperty rules -ErrorAction SilentlyContinue 43 | 44 | #If there are no iRules on this virtual server, then create a new array 45 | If (!$iRules){ 46 | $iRules = @() 47 | } 48 | 49 | #Check that the specified iRule is not already in the collection 50 | If ($iRules -match $iRuleFullName){ 51 | Write-Warning "The $Name virtual server already contains the $iRuleFullName iRule." 52 | $false 53 | } 54 | Else { 55 | $iRules += $iRuleFullName 56 | 57 | $URI = $F5Session.GetLink($virtualServer.selfLink) 58 | 59 | $JSONBody = @{rules=$iRules} | ConvertTo-Json 60 | 61 | Invoke-F5RestMethod -Method PATCH -Uri "$URI" -F5Session $F5Session -Body $JSONBody -ContentType 'application/json' -ErrorMessage "Failed to add the $iRuleFullName iRule to the $Name virtual server." -AsBoolean 62 | } 63 | } 64 | } 65 | } 66 | Name { 67 | $virtualservers = $Name | Get-VirtualServer -F5Session $F5Session -Partition $Partition 68 | 69 | if ($null -eq $virtualservers) { 70 | Write-Warning "No virtual servers found." 71 | $false 72 | } 73 | $virtualservers | Add-iRuleToVirtualServer -F5session $F5Session -iRuleName $iRuleName -Partition $Partition 74 | } 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /F5-LTM/Public/Remove-iRuleFromVirtualServer.ps1: -------------------------------------------------------------------------------- 1 | Function Remove-iRuleFromVirtualServer { 2 | <# 3 | .SYNOPSIS 4 | Remove an iRule from the specified virtual server 5 | .NOTES 6 | This function defaults to the /Common partition 7 | #> 8 | [cmdletBinding()] 9 | param( 10 | $F5Session=$Script:F5Session, 11 | 12 | [Parameter(Mandatory=$true,ParameterSetName='InputObject',ValueFromPipeline=$true)] 13 | [PSObject[]]$InputObject, 14 | 15 | [Alias("VirtualServer")] 16 | [Alias("VirtualServerName")] 17 | [Parameter(Mandatory=$true,ParameterSetName='Name',ValueFromPipeline=$true)] 18 | [string[]]$Name, 19 | [Parameter(Mandatory=$false)] 20 | [string]$Partition='Common', 21 | 22 | [Parameter(Mandatory=$true)] 23 | [string]$iRuleName, 24 | 25 | [switch]$PassThru 26 | ) 27 | begin { 28 | #Test that the F5 session is in a valid format 29 | Test-F5Session($F5Session) 30 | } 31 | process { 32 | switch($PSCmdLet.ParameterSetName) { 33 | InputObject { 34 | #Verify that the iRule exists on the F5 LTM 35 | $iRule = Get-iRule -F5session $F5Session -Name $iRuleName -Partition $Partition 36 | If ($null -eq $iRule){ 37 | Write-Error "The $iRuleName iRule does not exist in this F5 LTM." 38 | } else { 39 | $iRuleFullName = $iRule.fullPath 40 | foreach($virtualserver in $InputObject) { 41 | #Get the existing IRules on the virtual server 42 | [array]$iRules = $virtualserver | Select-Object -ExpandProperty rules -ErrorAction SilentlyContinue 43 | 44 | #If there are no iRules on this virtual server, then create a new array 45 | If (!$iRules){ 46 | $iRules = @() 47 | } 48 | 49 | #Check that the specified iRule is not already in the collection 50 | If ($iRules -match $iRuleFullName){ 51 | $iRules = $iRules | Where-Object { $_ -ne $iRuleFullName } 52 | 53 | $URI = $F5Session.GetLink($virtualServer.selfLink) 54 | 55 | $JSONBody = @{rules=$iRules} | ConvertTo-Json 56 | 57 | Invoke-F5RestMethod -Method PATCH -Uri "$URI" -F5Session $F5Session -Body $JSONBody -ContentType 'application/json' 58 | } 59 | Else { 60 | Write-Warning "The $($VirtualServer.name) virtual server does not contain the $iRuleFullName iRule." 61 | } 62 | } 63 | } 64 | } 65 | Name { 66 | $virtualserver = $Name | Get-VirtualServer -F5Session $F5Session -Partition $Partition 67 | 68 | if ($null -eq $virtualserver) { 69 | Write-Warning "No virtual servers found." 70 | } 71 | else { 72 | $virtualserver = $virtualserver | Remove-iRuleFromVirtualServer -F5session $F5Session -iRuleName $iRuleName -Partition $Partition 73 | If ($PassThru){ 74 | $virtualserver 75 | } 76 | } 77 | } 78 | } 79 | } 80 | } -------------------------------------------------------------------------------- /F5-LTM/Public/New-Pool.ps1: -------------------------------------------------------------------------------- 1 | Function New-Pool { 2 | <# 3 | .SYNOPSIS 4 | Create a new pool. Optionally, add pool members to the new pool 5 | 6 | .DESCRIPTION 7 | Expects the $MemberDefinitionList param to be an array of strings. 8 | Each string should contain a computer name and a port number, comma-separated. 9 | Optionally, it can contain a description of the member. 10 | 11 | .EXAMPLE 12 | # The MemberDefinitionList can accept a server name / IP address, a port number, a description and a route domain value. 13 | New-Pool -F5Session $F5Session -PoolName "MyPoolName" -MemberDefinitionList @("Server1,80,Web server,1","Server2,443,Another web server,2") 14 | If you don't use route domains, leave that value blank. 15 | 16 | #> 17 | [cmdletbinding()] 18 | param ( 19 | $F5Session=$Script:F5Session, 20 | [Alias('PoolName')] 21 | [Parameter(Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] 22 | [string[]]$Name, 23 | 24 | [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] 25 | [string]$Partition, 26 | 27 | [string]$Description, 28 | 29 | [ValidateSet('dynamic-ratio-member','dynamic-ratio-node','fastest-app-response','fastest-node','least-connections-member','least-connections-node','least-sessions','observed-member','observed-node','predictive-member','predictive-node','ratio-least-connections-member','ratio-least-connections-node','ratio-member','ratio-node','ratio-session','round-robin','weighted-least-connections-member','weighted-least-connections-node')] 30 | [Parameter(Mandatory=$true)] 31 | [string]$LoadBalancingMode, 32 | 33 | [Parameter(Mandatory=$false)] 34 | [string[]]$MemberDefinitionList=$null 35 | ) 36 | begin { 37 | #Test that the F5 session is in a valid format 38 | Test-F5Session($F5Session) 39 | 40 | $URI = ($F5Session.BaseURL + "pool") 41 | } 42 | process { 43 | foreach ($poolname in $Name) { 44 | $newitem = New-F5Item -Name $poolname -Partition $Partition 45 | #Check whether the specified pool already exists 46 | If (Test-Pool -F5session $F5Session -Name $newitem.Name -Partition $newitem.Partition){ 47 | Write-Error "The $($newitem.FullPath) pool already exists." 48 | } 49 | Else { 50 | #Start building the JSON for the action 51 | $JSONBody = @{name=$newitem.Name;partition=$newitem.Partition;description=$Description;loadBalancingMode=$LoadBalancingMode;members=@()} | ConvertTo-Json 52 | 53 | Invoke-F5RestMethod -Method POST -Uri "$URI" -F5Session $F5Session -Body $JSONBody -ContentType 'application/json' -ErrorMessage ("Failed to create the $($newitem.FullPath) pool.") -AsBoolean 54 | ForEach ($MemberDefinition in $MemberDefinitionList){ 55 | 56 | #split out comma-separated member definition values 57 | $Node,$PortNumber,$MemberDescription,$RouteDomain = $MemberDefinition -split ',' 58 | 59 | #If a route domain is included in the member defintion, then pass it to Add-PoolMember 60 | #JN: At a later date, I'd like to update Add-PoolMember to accept splated params 61 | If ($RouteDomain -ne ''){ 62 | $null = Add-PoolMember -F5Session $F5Session -PoolName $Name -Partition $Partition -Name $Node -PortNumber $PortNumber -Description $MemberDescription -Status Enabled -RouteDomain $RouteDomain 63 | } 64 | Else { 65 | $null = Add-PoolMember -F5Session $F5Session -PoolName $Name -Partition $Partition -Name $Node -PortNumber $PortNumber -Description $MemberDescription -Status Enabled 66 | } 67 | } 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /F5-LTM/F5-LTM.psd1: -------------------------------------------------------------------------------- 1 | # 2 | # Module manifest for module 'F5-LTM' 3 | # 4 | # Generated by: Joel Newton 5 | # 6 | # Generated on: 5/22/2015 7 | # 8 | 9 | @{ 10 | 11 | # Script module or binary module file associated with this manifest. 12 | RootModule = 'F5-LTM.psm1' 13 | 14 | # Version number of this module. 15 | ModuleVersion = '1.4.0' 16 | 17 | # ID used to uniquely identify this module 18 | GUID = 'f4649254-d64a-407d-822f-66a4618ed5fb' 19 | 20 | # Author of this module 21 | Author = 'Joel Newton' 22 | 23 | # Company or vendor of this module 24 | CompanyName = '' 25 | 26 | # Copyright statement for this module 27 | Copyright = '(c) 2023 Joel Newton. All rights reserved.' 28 | 29 | # Description of the functionality provided by this module 30 | Description = 'This module uses the REST API in the F5 LTM v11.6 and higher to query and manipulate the F5 LTM device.' 31 | 32 | # Minimum version of the Windows PowerShell engine required by this module 33 | PowerShellVersion = '3.0' 34 | 35 | # Name of the Windows PowerShell host required by this module 36 | # PowerShellHostName = '' 37 | 38 | # Minimum version of the Windows PowerShell host required by this module 39 | # PowerShellHostVersion = '' 40 | 41 | # Minimum version of Microsoft .NET Framework required by this module 42 | # DotNetFrameworkVersion = '' 43 | 44 | # Minimum version of the common language runtime (CLR) required by this module 45 | # CLRVersion = '' 46 | 47 | # Processor architecture (None, X86, Amd64) required by this module 48 | # ProcessorArchitecture = '' 49 | 50 | # Modules that must be imported into the global environment prior to importing this module 51 | # RequiredModules = @() 52 | 53 | # Assemblies that must be loaded prior to importing this module 54 | # RequiredAssemblies = @() 55 | 56 | # Script files (.ps1) that are run in the caller's environment prior to importing this module. 57 | # ScriptsToProcess = @() 58 | 59 | # Type files (.ps1xml) to be loaded when importing this module 60 | TypesToProcess = @('./TypeData/PoshLTM.Types.ps1xml') 61 | 62 | # Format files (.ps1xml) to be loaded when importing this module 63 | FormatsToProcess = @('./TypeData/PoshLTM.Format.ps1xml') 64 | 65 | # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess 66 | # NestedModules = @() 67 | 68 | # Functions to export from this module 69 | FunctionsToExport = '*' 70 | 71 | # Cmdlets to export from this module 72 | CmdletsToExport = '' 73 | 74 | # Variables to export from this module 75 | VariablesToExport = '*' 76 | 77 | # Aliases to export from this module 78 | AliasesToExport = '' 79 | 80 | # List of all modules packaged with this module 81 | # ModuleList = @() 82 | 83 | # List of all files packaged with this module 84 | # FileList = @() 85 | 86 | # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. 87 | PrivateData = @{ 88 | 89 | PSData = @{ 90 | 91 | # Tags applied to this module. These help with module discovery in online galleries. 92 | Tags = 'PSModule','F5','LTM','REST','API' 93 | 94 | # A URL to the license for this module. 95 | #LicenseUri = '' 96 | 97 | # A URL to the main website for this project. 98 | ProjectUri = 'https://github.com/joel74/POSH-LTM-Rest' 99 | 100 | # A URL to an icon representing this module. 101 | # IconUri = '' 102 | 103 | # ReleaseNotes of this module 104 | # ReleaseNotes = '' 105 | 106 | # External dependent modules of this module 107 | # ExternalModuleDependencies = '' 108 | 109 | } # End of PSData hashtable 110 | 111 | } # End of PrivateData hashtable 112 | 113 | # HelpInfo URI of this module 114 | # HelpInfoURI = '' 115 | 116 | # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. 117 | # DefaultCommandPrefix = '' 118 | 119 | } 120 | 121 | -------------------------------------------------------------------------------- /F5-LTM/Public/New-HealthMonitor.ps1: -------------------------------------------------------------------------------- 1 | Function New-HealthMonitor { 2 | <# 3 | .SYNOPSIS 4 | Create a new health monitor 5 | 6 | .EXAMPLE 7 | New-HealthMonitor -F5Session $F5Session -Name "/Common/test123" -Type http -Receive '^HTTP.1.[0-2]\s([2|3]0[0-9])' -Send 'HEAD /host.ashx?isup HTTP/1.1\r\nHost: Test123.dyn-intl.com\r\nConnection: close\r\n\r\n' 8 | 9 | New-HealthMonitor -F5Session $F5Session -Name "/Common/test123" -Type http -Receive '^HTTP.1.[0-2]\s([2|3]0[0-9])' -Send 'HEAD /host.ashx?isup HTTP/1.1\r\nHost: Test123.dyn-intl.com\r\nConnection: close\r\n\r\n' -Destination '*.80' 10 | 11 | New-HealthMonitor -F5Session $F5Session -Name "/Common/test123" -Type http -Receive '^HTTP.1.[0-2]\s([2|3]0[0-9])' -Send 'HEAD /host.ashx?isup HTTP/1.1\r\nHost: Test123.dyn-intl.com\r\nConnection: close\r\n\r\n' -Destination '10.1.1.1.80' -Description 'My Test Monitor' 12 | #> 13 | [cmdletbinding()] 14 | param ( 15 | $F5Session=$Script:F5Session, 16 | 17 | [Alias('HealthMonitorName')] 18 | [Alias('MonitorName')] 19 | [Parameter(Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] 20 | [string[]]$Name, 21 | 22 | [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] 23 | [string]$Partition, 24 | 25 | [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)] 26 | [string]$Type, 27 | 28 | [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] 29 | [string]$Receive, 30 | 31 | [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] 32 | [string]$Send, 33 | 34 | [Parameter(ValueFromPipelineByPropertyName=$true)] 35 | [int]$Interval=5, 36 | 37 | [Parameter(ValueFromPipelineByPropertyName=$true)] 38 | [int]$Timeout=16, 39 | 40 | [Parameter(ValueFromPipelineByPropertyName=$true)] 41 | [string]$Destination='*.*', 42 | 43 | [Parameter(ValueFromPipelineByPropertyName=$true)] 44 | [string]$Description='', 45 | 46 | [switch] 47 | $Passthru 48 | ) 49 | begin { 50 | #Test that the F5 session is in a valid format 51 | Test-F5Session($F5Session) 52 | } 53 | process { 54 | $URI = $F5Session.BaseURL + "monitor/$Type" 55 | foreach($monitorname in $Name) { 56 | $newitem = New-F5Item -Name $monitorname -Partition $Partition 57 | #Check whether the specified pool already exists 58 | If (Test-HealthMonitor -F5session $F5Session -Name $newitem.Name -Partition $newitem.Partition -Type $Type){ 59 | Write-Error "The /$Type$($newitem.FullPath) health monitor already exists." 60 | } else { 61 | #Start building the JSON for the action 62 | $JSONBody = @{name=$newitem.Name;partition=$newitem.Partition;recv=$Receive;send=$Send;interval=$Interval;timeout=$Timeout;destination=$Destination;description=$Description} | 63 | ConvertTo-Json 64 | 65 | # Caused by a bug in ConvertTo-Json https://windowsserver.uservoice.com/forums/301869-powershell/suggestions/11088243-provide-option-to-not-encode-html-special-characte 66 | # '<', '>', ''' and '&' are replaced by ConvertTo-Json to \\u003c, \\u003e, \\u0027, and \\u0026. The F5 API doesn't understand this. Change them back. 67 | $ReplaceChars = @{ 68 | '\\u003c' = '<' 69 | '\\u003e' = '>' 70 | '\\u0027' = "'" 71 | '\\u0026' = "&" 72 | } 73 | 74 | foreach ($Char in $ReplaceChars.GetEnumerator()) 75 | { 76 | $JSONBody = $JSONBody -replace $Char.Key, $Char.Value 77 | } 78 | $newmonitor = Invoke-F5RestMethod -Method POST -Uri "$URI" -F5Session $F5Session -Body $JSONBody -ContentType 'application/json' -ErrorMessage "Failed to create the /$Type$($newitem.FullPath) health monitor." 79 | if ($Passthru) { 80 | Get-HealthMonitor -F5Session $F5Session -Name $newmonitor.name -Partition $newmonitor.partition -Type $Type 81 | } 82 | } 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /F5-LTM/Public/Get-VirtualServer.ps1: -------------------------------------------------------------------------------- 1 | Function Get-VirtualServer{ 2 | <# 3 | .SYNOPSIS 4 | Retrieve specified virtual server(s) 5 | 6 | .EXAMPLE 7 | #Retrieve all virtual servers with a list of assigned iRules for each 8 | $VS_iRules = Get-VirtualServer | 9 | ForEach { 10 | New-Object psobject -Property @{ 11 | Name = $_.name; 12 | Partition = $_.partition; 13 | Rules = @{} 14 | } 15 | } 16 | 17 | $VS_iRules | ForEach { $_.Rules = (Get-VirtualServer -Name $_.Name -Partition $_.Partition | Select-Object -ExpandProperty rules -ErrorAction SilentlyContinue ) } 18 | 19 | #> 20 | [cmdletBinding()] 21 | param ( 22 | $F5Session=$Script:F5Session, 23 | 24 | [Alias("VirtualServerName")] 25 | [Parameter(Mandatory=$false,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] 26 | [string[]]$Name='', 27 | 28 | [Alias('iApp')] 29 | [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] 30 | [string]$Application='', 31 | 32 | [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] 33 | [string]$Partition, 34 | 35 | [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] 36 | [PoshLTM.F5Address[]]$Address 37 | 38 | ) 39 | begin { 40 | #Test that the F5 session is in a valid format 41 | Test-F5Session($F5Session) 42 | 43 | Write-Verbose "NB: Virtual server names are case-specific." 44 | } 45 | process { 46 | 47 | foreach ($vsname in $Name) { 48 | 49 | $URI = $F5Session.BaseURL + 'virtual/{0}?expandSubcollections=true' -f (Get-ItemPath -Name $vsname -Application $Application -Partition $Partition) 50 | $JSON = Invoke-F5RestMethod -Method Get -Uri $URI -F5Session $F5Session 51 | if ($JSON.items -or $JSON.name) { 52 | $items = Invoke-NullCoalescing {$JSON.items} {$JSON} 53 | if(![string]::IsNullOrWhiteSpace($Application)) { 54 | $items = $items | Where-Object {$_.fullPath -eq "/$($_.partition)/$Application.app/$($_.name)"} 55 | } 56 | 57 | #If an IP address was specified, filter the results by that 58 | If ($Address){ 59 | $items = $items | Where-Object {$_.Destination -match $Address} 60 | } 61 | 62 | <# 63 | JN: the expandSubcollections=true param should be a better way to expand subcollections and so this subcollection expansion logic wouldn't be needed. 64 | 65 | #Unless requested subcollections will be included 66 | #Excluding subcollections has a significant performance increase 67 | If (!$ExcludeSubcollections) { 68 | 69 | #Retrieve all subcollections' contents 70 | $subcollections = [Array] $items | Get-Member -MemberType NoteProperty | % Name | % { $items.$_ } | Where { $_.isSubcollection -eq 'True' } 71 | 72 | #Add properties for policies and profiles 73 | $items | Add-Member -NotePropertyName 'policies' -NotePropertyValue @() 74 | $items | Add-Member -NotePropertyName 'profiles' -NotePropertyValue @() 75 | 76 | ForEach ($sub in $subcollections) 77 | { 78 | 79 | #Retrieve the virtual server name from the link 80 | $tmpArray = [string]($sub.link) -split "/" 81 | $tmpArray = ($tmpArray[$tmpArray.Length-2]).Split("~") 82 | $virtualServerName = $tmpArray[$tmpArray.Length-1] 83 | 84 | #Expand each subcollection 85 | $JSON = Invoke-F5RestMethod -Method Get -Uri ($sub.link -replace 'localhost',$F5Session.Name) -F5Session $F5Session 86 | 87 | #If the subcollection contains items, then add them to the JSON to return 88 | #Make sure to add them to the corresponding virtual server 89 | If ($JSON.items){ 90 | 91 | #Retrieve the name for the collection from the segment of the 'kind' value preceding the collection. 92 | #JN: There may be a better way to determine this 93 | $tmpArray = [string]($JSON.kind) -split ":" 94 | $collName = [string]$tmpArray[$tmpArray.length-2] 95 | 96 | #Add the contents of the subcollection 97 | ($items | Where-Object Name -CContains $virtualServerName).$collName = $JSON.items 98 | } 99 | } 100 | } #End of subcollection gathering 101 | #> 102 | $items | Add-ObjectDetail -TypeName 'PoshLTM.VirtualServer' 103 | } 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /F5-LTM/TypeData/PoshLTM.Types.ps1xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | PoshLTM.ProfileHttp 5 | 6 | 7 | Name 8 | 9 | 10 | 11 | Partition 12 | 13 | 14 | 15 | Description 16 | 17 | 18 | 19 | insertXforwardedFor 20 | 21 | 22 | 23 | encryptCookieSecret 24 | 25 | 26 | 27 | encryptCookies 28 | 29 | 30 | 31 | redirectRewrite 32 | 33 | 34 | 35 | serverAgentName 36 | 37 | 38 | 39 | 40 | 41 | PoshLTM.HealthMonitor 42 | 43 | 44 | Name 45 | 46 | 47 | 48 | Partition 49 | 50 | 51 | 52 | Type 53 | 54 | 55 | 56 | Recv 57 | 58 | 59 | 60 | Send 61 | 62 | 63 | 64 | 65 | 66 | PoshLTM.iRule 67 | 68 | 69 | Name 70 | 71 | 72 | 73 | Partition 74 | 75 | 76 | 77 | Definition 78 | 79 | 80 | 81 | 82 | 83 | PoshLTM.Node 84 | 85 | 86 | Name 87 | 88 | 89 | 90 | Partition 91 | 92 | 93 | 94 | Address 95 | 96 | 97 | 98 | Description 99 | 100 | 101 | 102 | 103 | 104 | PoshLTM.Pool 105 | 106 | 107 | Name 108 | 109 | 110 | 111 | Partition 112 | 113 | 114 | 115 | Mode 116 | 117 | 118 | 119 | ApplicationService 120 | 121 | 122 | 123 | 124 | 125 | PoshLTM.PoolMember 126 | 127 | 128 | Name 129 | 130 | 131 | 132 | Partition 133 | 134 | 135 | 136 | Address 137 | 138 | 139 | 140 | Description 141 | 142 | 143 | 144 | Monitor 145 | 146 | 147 | 148 | Session 149 | 150 | 151 | 152 | State 153 | 154 | 155 | 156 | 157 | 158 | PoshLTM.VirtualServer 159 | 160 | 161 | Name 162 | 163 | 164 | 165 | Partition 166 | 167 | 168 | 169 | Description 170 | 171 | 172 | 173 | Source 174 | 175 | 176 | 177 | Destination 178 | 179 | 180 | 181 | Pool 182 | 183 | 184 | 185 | Enabled 186 | 187 | 188 | 189 | 190 | -------------------------------------------------------------------------------- /F5-LTM/Public/New-Node.ps1: -------------------------------------------------------------------------------- 1 | Function New-Node { 2 | <# 3 | .SYNOPSIS 4 | Create Node(s) 5 | 6 | .EXAMPLE 7 | New-Node -Address 192.168.1.42 8 | #> 9 | [cmdletbinding()] 10 | param ( 11 | $F5Session=$Script:F5Session, 12 | 13 | [Parameter(Mandatory=$true,ParameterSetName='Address',ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] 14 | [PoshLTM.F5Address[]]$Address, 15 | 16 | [Parameter(Mandatory=$true,ParameterSetName='FQDN',ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] 17 | [string[]]$FQDN, 18 | 19 | [Parameter(Mandatory=$true,ParameterSetName='FQDN',ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] 20 | [ValidateSet('ipv4','ipv6')] 21 | $AddressType, 22 | 23 | [Parameter(Mandatory=$true,ParameterSetName='FQDN',ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] 24 | [ValidateSet('enabled','disabled')] 25 | $AutoPopulate, 26 | 27 | [Parameter(Mandatory=$false,ParameterSetName='FQDN',ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] 28 | [int]$Interval=3600, 29 | 30 | [Parameter(Mandatory=$false,ParameterSetName='FQDN',ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] 31 | [int]$DownInterval=5, 32 | 33 | [Alias('ComputerName')] 34 | [Alias('NodeName')] 35 | [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] 36 | [string[]]$Name='', 37 | 38 | [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] 39 | [string]$Partition, 40 | 41 | [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] 42 | [string[]]$Description='', 43 | 44 | [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] 45 | [string[]]$Monitor='', 46 | 47 | [switch]$Passthru 48 | ) 49 | begin { 50 | #Test that the F5 session is in a valid format 51 | Test-F5Session($F5Session) 52 | 53 | $URI = ($F5Session.BaseURL + "node") 54 | } 55 | process { 56 | switch($PSCmdLet.ParameterSetName) { 57 | Address { 58 | #Process all nodes with IP addresses 59 | for ([int]$a=0; $a -lt $Address.Count; $a++) { 60 | $itemname = $Name[$a] 61 | if ([string]::IsNullOrWhiteSpace($itemname)) { 62 | $itemname = $Address[$a].ToString() 63 | } 64 | $newitem = New-F5Item -Name $itemname -Partition $Partition 65 | #Check whether the specified node already exists 66 | If (Test-Node -F5session $F5Session -Name $newitem.Name -Partition $newitem.Partition){ 67 | Write-Error "The $($newitem.FullPath) node already exists." 68 | } else { 69 | #Start building the JSON for the action 70 | $JSONBody = @{address=$Address[$a].ToString();name=$newitem.Name;partition=$newitem.Partition;description=$Description[$a];monitor=$($Monitor -join ' and ')} | ConvertTo-Json 71 | 72 | Invoke-F5RestMethod -Method POST -Uri "$URI" -F5Session $F5Session -Body $JSONBody -ContentType 'application/json' | 73 | Out-Null 74 | if ($Passthru) { 75 | Get-Node -F5Session $F5Session -Name $newitem.Name -Partition $newitem.Partition 76 | } 77 | } 78 | } 79 | } 80 | 81 | FQDN { 82 | #Process all nodes with fully qualified domain names 83 | for ([int]$a=0; $a -lt $Name.Count; $a++) { 84 | $itemname = $Name[$a].ToString() 85 | if ([string]::IsNullOrWhiteSpace($itemname)) { 86 | $itemname = $FQDN[$a].ToString() 87 | } 88 | $newitem = New-F5Item -Name $itemname -Partition $Partition 89 | $itemfqdn = $FQDN[$a] 90 | #Check whether the specified node already exists 91 | If (Test-Node -F5session $F5Session -Name $newitem.Name -Partition $newitem.Partition){ 92 | Write-Error "The $($newitem.FullPath) node already exists." 93 | } else { 94 | #Start building the JSON for the action 95 | $JSON_FQDN = @{name=$itemfqdn;'address-family'=$AddressType;autopopulate=$AutoPopulate;interval=$Interval;'down-interval'=$DownInterval} # | ConvertTo-Json 96 | $JSONBody = @{name=$itemname;fqdn=$JSON_FQDN;partition=$newitem.Partition;description=$Description[$a];monitor=$($Monitor -join ' and ')} | ConvertTo-Json 97 | Invoke-F5RestMethod -Method POST -Uri "$URI" -F5Session $F5Session -Body $JSONBody -ContentType 'application/json' | 98 | Out-Null 99 | if ($Passthru) { 100 | Get-Node -F5Session $F5Session -Name $newitem.Name -Partition $newitem.Partition 101 | } 102 | } 103 | } 104 | } 105 | } 106 | } 107 | } -------------------------------------------------------------------------------- /F5-LTM/TypeData/PoshLTM.Types.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using System.Net.Sockets; 4 | using System.Text.RegularExpressions; 5 | namespace PoshLTM 6 | { 7 | public struct F5Address 8 | { 9 | public static F5Address Any = IPAddress.Any; 10 | public IPAddress IPAddress; 11 | public int? RouteDomain; 12 | 13 | public F5Address(string address) 14 | { 15 | RouteDomain = null; 16 | IPAddress = null; 17 | string hostname = address; 18 | // Extract a RouteDomain, if applicable 19 | if (Regex.IsMatch(address,"%[0-9]+$")) 20 | { 21 | hostname = address.Split('%')[0]; 22 | RouteDomain = int.Parse(address.Split('%')[1]); 23 | } 24 | // IPv4 Addresses always start with a number, server names can not 25 | if (Regex.IsMatch(hostname,"^[0-9]")) 26 | { 27 | IPAddress = IPAddress.Parse(hostname); 28 | } 29 | else if (address == "any6") 30 | { 31 | IPAddress = IPAddress.Any; 32 | } 33 | else 34 | { 35 | // Resolve hostname 36 | // IPv6 Addresses do not always start with a number, but resolve nicely 37 | foreach (IPAddress IPA in Dns.GetHostAddresses(hostname)) 38 | { 39 | // This avoids (but does not prevent) getting no address at all 40 | IPAddress = IPA; 41 | // This applies a bias in favor of IPv4 or IPv6 addresses that are NOT LinkLocal which often have a physical NIC ScopeId, not to be confused with a RouteDomain 42 | if (IPA.AddressFamily == AddressFamily.InterNetwork || (IPA.AddressFamily == AddressFamily.InterNetworkV6 && !IPA.IsIPv6LinkLocal)) 43 | { 44 | break; 45 | } 46 | } 47 | } 48 | } 49 | 50 | #region Override Equals 51 | 52 | // https://msdn.microsoft.com/ru-ru/library/ms173147(v=vs.80).aspx 53 | public override bool Equals(Object obj) 54 | { 55 | // If parameter is null return false. 56 | if (obj == null) 57 | { 58 | return false; 59 | } 60 | if (obj is F5Address) 61 | { 62 | return this.Equals((F5Address)obj); 63 | } 64 | if (obj is IPAddress) 65 | { 66 | var f5address = new F5Address(((IPAddress)obj).ToString()); 67 | return this.Equals(f5address); 68 | } 69 | if (obj is string) 70 | { 71 | var f5address = new F5Address((string)obj); 72 | return this.Equals(f5address); 73 | } 74 | return false; 75 | } 76 | 77 | public bool Equals(F5Address other) 78 | { 79 | return IPAddress.Equals(other.IPAddress) && RouteDomain.Equals(other.RouteDomain); 80 | } 81 | 82 | // Required to override Equals 83 | public override int GetHashCode() 84 | { 85 | return this.ToString().GetHashCode(); 86 | } 87 | 88 | #endregion 89 | 90 | // This differs from Equals: it will match on IP alone if no RouteDomain criteria is specified. 91 | public bool IsMatch(string address) 92 | { 93 | // Built-in matching for the default (Any) filter 94 | if (this.Equals(Any)) { 95 | return true; 96 | } 97 | if (RouteDomain.HasValue) 98 | { 99 | return this.ToString() == address; 100 | } 101 | else 102 | { 103 | F5Address f5address = new F5Address(address); 104 | return IPAddress.ToString() == f5address.IPAddress.ToString(); 105 | } 106 | } 107 | 108 | public override string ToString() 109 | { 110 | return String.Format("{0}{1:\\%0}", IPAddress, RouteDomain); 111 | } 112 | 113 | public static implicit operator F5Address(IPAddress value) 114 | { 115 | return new F5Address(value.ToString()); 116 | } 117 | public static implicit operator F5Address(string value) 118 | { 119 | return new F5Address(value); 120 | } 121 | public static implicit operator IPAddress(F5Address value) 122 | { 123 | return value.IPAddress; 124 | } 125 | public static implicit operator string(F5Address value) 126 | { 127 | return String.Format("{0}{1:\\%0}", value.IPAddress, value.RouteDomain); 128 | } 129 | public static bool IsMatch(F5Address filter, string address) 130 | { 131 | return filter.IsMatch(address); 132 | } 133 | public static bool IsMatch(F5Address[] filter, string address) 134 | { 135 | foreach(F5Address f in filter) 136 | { 137 | if (f.IsMatch(address)) 138 | { 139 | return true; 140 | } 141 | } 142 | return false; 143 | } 144 | } 145 | } -------------------------------------------------------------------------------- /F5-LTM/Public/Set-iRule.ps1: -------------------------------------------------------------------------------- 1 | Function Set-iRule 2 | { 3 | <# 4 | .SYNOPSIS 5 | Create or updates an iRule 6 | .DESCRIPTION 7 | Can create a new, and update an existing iRule. 8 | Removes iRule from VirtualServers and adds them back again after uploading new iRule. 9 | The command supports -whatif 10 | .PARAMETER iRuleContent 11 | The content of the iRule (as a string). 12 | Alias: iRuleContent 13 | .PARAMETER Partition 14 | The partition on the F5 to put the iRule on. The full path will be /Partition/iRuleName. 15 | .PARAMETER OverWrite 16 | Overwrite the iRule already present on F5. It will: 17 | - check if any VirtualServers have the iRule configured 18 | - remove the iRule from those VirtualServers 19 | - delete old and upload new iRule 20 | - add the iRule back to the VirtualServers. 21 | .EXAMPLE 22 | Set-iRule -name 'NameThatMakesSense' -apiAnonymous $ObjectofStrings 23 | #> 24 | [cmdletbinding()] 25 | param ( 26 | $F5Session = $Script:F5Session, 27 | [Parameter(Mandatory)] 28 | [string]$Name, 29 | [Alias('apiAnonymous')] 30 | [Parameter(Mandatory)] 31 | [string]$iRuleContent, 32 | [string]$Partition = 'Common', 33 | [Alias('Overwrite')] 34 | [switch]$Force 35 | ) 36 | 37 | begin { 38 | Test-F5Session -F5Session ($F5Session) 39 | 40 | $URI = ($F5Session.BaseURL + 'rule') 41 | } 42 | 43 | process { 44 | $newitem = New-F5Item -Name $Name 45 | 46 | $kind = 'tm:ltm:rule:rulestate' 47 | 48 | $iRuleFullName = "/$Partition/$Name" 49 | 50 | $JSONBody = @{ 51 | kind = $kind 52 | name = $newitem.Name 53 | partition = $Partition 54 | fullPath = $newitem.Name 55 | apiAnonymous = $iRuleContent 56 | } 57 | 58 | $JSONBody = $JSONBody | ConvertTo-Json -Compress 59 | 60 | # Caused by a bug in ConvertTo-Json https://windowsserver.uservoice.com/forums/301869-powershell/suggestions/11088243-provide-option-to-not-encode-html-special-characte 61 | # '<', '>', ''' and '&' are replaced by ConvertTo-Json to \\u003c, \\u003e, \\u0027, and \\u0026. The F5 API doesn't understand this. Change them back. 62 | $ReplaceChars = @{ 63 | '\\u003c' = '<' 64 | '\\u003e' = '>' 65 | '\\u0027' = "'" 66 | '\\u0026' = "&" 67 | } 68 | 69 | foreach ($Char in $ReplaceChars.GetEnumerator()) 70 | { 71 | $JSONBody = $JSONBody -replace $Char.Key, $Char.Value 72 | } 73 | 74 | $iRuleonServer = Get-iRule -Name $newitem.Name -Partition $Partition -ErrorAction SilentlyContinue 75 | 76 | if ($iRuleonServer) 77 | { 78 | if ($iRuleonServer.apiAnonymous -eq $iRuleContent) 79 | { 80 | Write-Verbose -Message 'iRule on server is already in place' 81 | $iRulesDifferent = $false 82 | $true 83 | } 84 | 85 | else 86 | { 87 | $iRulesDifferent = $True 88 | 89 | if ($Force) 90 | { 91 | Write-Verbose -Message 'iRule on server is different from current version, and Force flag was set. Removing the iRule from VirtualServer and adding the new one.' 92 | 93 | $VirtualServers = Get-VirtualServer | Where-Object -Property rules -EQ -Value $iRuleFullName 94 | 95 | foreach ($Virtualserver in $VirtualServers) 96 | { 97 | if ($pscmdlet.ShouldProcess($Virtualserver.Name, "Remove iRule $Name from VirtualServer")) 98 | { 99 | $Null = $Virtualserver | Remove-iRuleFromVirtualServer -iRuleName $Name 100 | } 101 | } 102 | 103 | if ($pscmdlet.ShouldProcess($F5Session.Name, "Deleting iRule $Name")) 104 | { 105 | $URIOldiRule = $F5Session.GetLink($iRuleonServer.selfLink) 106 | Invoke-F5RestMethod -Method DELETE -URI $URIOldiRule -F5Session $F5Session 107 | } 108 | } 109 | 110 | else 111 | { 112 | Write-Warning -Message 'iRule on server is different from current version, use -Force to overwrite current iRule.' 113 | } 114 | } 115 | } 116 | 117 | if ( (-not $iRuleonServer) -or ($iRuleonServer -and $iRulesDifferent -and $Force) ) 118 | { 119 | if ($pscmdlet.ShouldProcess($F5Session.Name, "Uploading iRule $Name")) 120 | { 121 | Invoke-F5RestMethod -Method POST -URI "$URI" -F5Session $F5Session -Body $JSONBody -ContentType 'application/json' -AsBoolean 122 | } 123 | 124 | foreach ($Virtualserver in $VirtualServers) 125 | { 126 | if ($pscmdlet.ShouldProcess($Virtualserver.Name, "Add iRule $Name")) 127 | { 128 | $Null = Get-VirtualServer -name $Virtualserver.Name | Add-iRuleToVirtualServer -iRuleName $Name 129 | } 130 | } 131 | } 132 | } 133 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # POSH-LTM-Rest 2 | This PowerShell module uses the F5 LTM REST API to manipulate and query pools, pool members, virtual servers and iRules. 3 | It is built to work with the following BIG-IP versions: 4 | * 11.5.1 Build 8.0.175 Hotfix 8 and later 5 | * 11.6.0 Build 5.0.429 Hotfix 4 and later 6 | * All versions of LTM from 12.0.0 onward. 7 | 8 | It requires PowerShell v3 or higher. 9 | It is set to negotiate connections using TLS 1.2. TLS 1.2 is only supported on .NET Framework 4.5+. 10 | 11 | It includes a Validation.cs class file (based on code posted by Brian Scholer on www.briantist.com) to allow for using the REST API with LTM devices using self-signed SSL certificates. 12 | 13 | Setup: 14 | Install the latest version of the module either by calling 'Install-Module F5-LTM' to retrieve it from the PowerShell Gallery (assuming you're using PSGet, included in PowerShell 5 and later). By default, PSGallery is an untrusted repository, so you may be prompted for confirmation. If you don't have PowerShell 5 / PSGet, use the Gist install script available at https://gist.github.com/joel74/f5acb78ca7dbe0d87bc95cab98de1388 15 | 16 | The module contains the following functions. 17 | 18 | * Add-iRuleToVirtualServer 19 | * Add-PoolMember 20 | * Add-PoolMonitor 21 | * Disable-Node 22 | * Disable-PoolMember 23 | * Disable-VirtualServer 24 | * Enable-Node 25 | * Enable-PoolMember 26 | * Enable-VirtualServer 27 | * Get-Application 28 | * Get-BIGIPPartition 29 | * Get-CurrentConnectionCount (deprecated; use __Get-PoolMemberStats | Select-Object -ExpandProperty 'serverside.curConns'__) 30 | * Get-F5Session (will be deprecated in future versions. use __New-F5Session__) 31 | * Get-F5Status 32 | * Get-HealthMonitor 33 | * Get-HealthMonitorType 34 | * Get-iRule 35 | * Get-iRuleCollection (deprecated; use __Get-iRule__) 36 | * Get-Node 37 | * Get-Pool 38 | * Get-NodeStats 39 | * Get-PoolList (deprecated; use __Get-Pool__) 40 | * Get-PoolMember 41 | * Get-PoolMemberCollection (deprecated; use __Get-PoolMember__) 42 | * Get-PoolMemberCollectionStatus 43 | * Get-PoolMemberDescription (deprecated; use __Get-PoolMember__) 44 | * Get-PoolMemberIP (deprecated; use __Get-PoolMember__) 45 | * Get-PoolMemberStats 46 | * Get-PoolMemberStatus (deprecated; use __Get-PoolMember__) 47 | * Get-PoolMonitor 48 | * Get-PoolsForMember 49 | * Get-ProfileHttp 50 | * Get-StatusShape 51 | * Get-VirtualServer 52 | * Get-VirtualServeriRuleCollection (deprecated; use __Get-VirtualServer | Where rules | Select -ExpandProperty rules__) 53 | * Get-VirtualServerList (deprecated; use __Get-VirtualServer__) 54 | * Invoke-RestMethodOverride 55 | * New-Application 56 | * New-F5Session 57 | * New-HealthMonitor 58 | * New-Node 59 | * New-Pool 60 | * New-ProfileHttp 61 | * New-VirtualServer 62 | * Remove-Application 63 | * Remove-F5Session 64 | * Remove-HealthMonitor 65 | * Remove-iRule 66 | * Remove-iRuleFromVirtualServer 67 | * Remove-Pool 68 | * Remove-PoolMember 69 | * Remove-PoolMonitor 70 | * Remove-ProfileRamCache 71 | * Remove-Node 72 | * Remove-ProfileHttp 73 | * Remove-VirtualServer 74 | * Set-iRule (used to both create and update) 75 | * Set-PoolLoadBalancingMode (deprecated; use __Set-Pool__) 76 | * Set-PoolMemberDescription 77 | * Set-Pool 78 | * Set-VirtualServer 79 | * Sync-DeviceToGroup 80 | * Test-F5Session 81 | * Test-Functionality 82 | * Test-HealthMonitor 83 | * Test-Node 84 | * Test-Pool 85 | * Test-ProfileHttp 86 | * Test-VirtualServer 87 | 88 | Nearly all of the functions require an F5 session object to manipulate the F5 LTM via the REST API. 89 | use the New-F5Session function to create this object. This function expects the following parameters: 90 | * The name or IP address of the F5 LTM device 91 | * A credential object for a user with rights to use the REST API. 92 | 93 | You can create a credential object using 'Get-Credential' and entering the username and password at the prompts, or programmatically like this: 94 | ``` 95 | $secpasswd = ConvertTo-SecureString "PlainTextPassword" -AsPlainText -Force 96 | $mycreds = New-Object System.Management.Automation.PSCredential "username", $secpasswd 97 | ``` 98 | Thanks to Kotesh Bandhamravuri and his blog entry https://docs.microsoft.com/en-us/archive/blogs/koteshb/powershell-how-to-create-a-pscredential-object for this snippet. 99 | 100 | The first time New-F5Session is called, it creates a script-scoped $F5Session object that is referenced by the functions that require an F5 session. If an F5 session object is passed to one of these functions, that will be used in place of the script-scoped $F5Session object. 101 | 102 | To create a F5 session object to store locally, instead of in the script scope, use the -PassThrough switch when calling New-F5Session, and the function will return the object. 103 | To overwrite the F5 session in the script scope, use the -Default switch when calling New-F5Session. 104 | 105 | There is a function called Test-Functionality that takes a pool name, a virtual server name, an IP address for the virtual server, and a computer as a pool member, and validates nearly all the functions in the module. Make sure that you don't use an existing pool name or virtual server name. 106 | Here is an example of how to use this function: 107 | 108 | ``` 109 | #Create an F5 session 110 | New-F5Session -LTMName $MyLTM_IP -LTMCredentials $MyLTMCreds 111 | Test-Functionality -TestVirtualServer 'TestVirtServer01' -TestVirtualServerIP $VirtualServerIP -TestPool 'TestPool01' -PoolMember $SomeComputerName 112 | ``` 113 | -------------------------------------------------------------------------------- /F5-LTM/Public/Add-PoolMember.ps1: -------------------------------------------------------------------------------- 1 | Function Add-PoolMember{ 2 | <# 3 | .SYNOPSIS 4 | Add a computer to a pool as a member 5 | .LINK 6 | [Modifying pool members](https://devcentral.f5.com/questions/modifying-pool-members-through-rest-api) 7 | [Add a pool with an existing node member](https://devcentral.f5.com/questions/add-a-new-pool-with-an-existing-node) 8 | #> 9 | [cmdletBinding()] 10 | param ( 11 | $F5Session=$Script:F5Session, 12 | 13 | [Parameter(Mandatory=$true,ParameterSetName='InputObject',ValueFromPipeline=$true)] 14 | [Alias("Pool")] 15 | [PSObject[]]$InputObject, 16 | 17 | [Parameter(Mandatory=$true,ParameterSetName='PoolName',ValueFromPipeline=$true)] 18 | [string[]]$PoolName, 19 | 20 | [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] 21 | [string]$Partition, 22 | 23 | [Parameter(Mandatory=$false)] 24 | [PoshLTM.F5Address]$Address=[PoshLTM.F5Address]::Any, 25 | 26 | [Parameter(Mandatory=$false)] 27 | [string]$Name, 28 | 29 | [Parameter(Mandatory=$true)] 30 | [ValidateRange(0,65535)] 31 | [int]$PortNumber, 32 | 33 | [Parameter(Mandatory=$false)] 34 | [string]$Description, 35 | 36 | [ValidateSet("Enabled","Disabled")] 37 | [Parameter(Mandatory=$true)]$Status, 38 | 39 | [Alias('iApp')] 40 | [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] 41 | [string]$Application='', 42 | 43 | [Parameter(Mandatory=$false)] 44 | [int]$RouteDomain 45 | 46 | ) 47 | 48 | begin { 49 | #Test that the F5 session is in a valid format 50 | Test-F5Session($F5Session) 51 | 52 | #If both Address and RouteDomain are passed in, then append RouteDomain to Address 53 | if ($Address -and $RouteDomain) { 54 | $Address = "{0}%{1}" -f $Address.IPAddress.IPAddressToString, $RouteDomain.ToString() 55 | } 56 | 57 | If (!$Name -and !$Address){ 58 | Write-Error 'Either a name or an IP address is required.' 59 | } 60 | 61 | #Strip out any port info when checking for an existing node 62 | $NodeName = $Name -replace ':\d+$','' 63 | 64 | $ExistingNode = Get-Node -F5Session $F5Session -Address $Address -Name $NodeName -Partition $Partition -ErrorAction SilentlyContinue 65 | } 66 | 67 | process { 68 | switch ($PSCmdLet.ParameterSetName) { 69 | 'InputObject' { 70 | switch ($InputObject.kind) { 71 | "tm:ltm:pool:poolstate" { 72 | $AddressString = $Address.ToString() 73 | if ($Address -ne [PoshLTM.F5Address]::Any) { 74 | # Default name to IPAddress 75 | if (!$Name) { 76 | $Name = '{0}:{1}' -f $AddressString, $PortNumber 77 | } 78 | } 79 | # Append port number if not already present 80 | if ($Name -notmatch ':\d+$') { 81 | $Name = '{0}:{1}' -f $Name,$PortNumber 82 | } 83 | foreach($pool in $InputObject) { 84 | if (!$Partition) { 85 | $Partition = $pool.partition 86 | } 87 | $JSONBody = @{name=$Name;partition=$Partition;address=$AddressString;description=$Description} 88 | if ($ExistingNode) { 89 | # Node exists, just add using name 90 | $JSONBody = @{name=('{0}:{1}' -f $ExistingNode.name,$PortNumber);partition=('{0}' -f $Partition);description=$Description} 91 | } # else the node will be created 92 | $JSONBody = $JSONBody | ConvertTo-Json 93 | $MembersLink = $F5session.GetLink($pool.membersReference.link) 94 | Invoke-F5RestMethod -Method POST -Uri "$MembersLink" -F5Session $F5Session -Body $JSONBody -ContentType 'application/json' -ErrorMessage "Failed to add $Name to $($pool.name)." | Add-ObjectDetail -TypeName 'PoshLTM.PoolMember' 95 | 96 | #After adding to the pool, make sure the member status is set as specified 97 | If ($Status -eq "Enabled"){ 98 | 99 | $pool | Get-PoolMember -F5Session $F5Session -Address $AddressString -Name $Name -Application $Application | Enable-PoolMember -F5session $F5Session | Out-Null 100 | } 101 | ElseIf ($Status -eq "Disabled"){ 102 | $pool | Get-PoolMember -F5Session $F5Session -Address $AddressString -Name $Name -Application $Application | Disable-PoolMember -F5session $F5Session | Out-Null 103 | 104 | } 105 | } 106 | } 107 | } 108 | } 109 | 'PoolName' { 110 | foreach($pName in $PoolName) { 111 | 112 | Get-Pool -F5Session $F5Session -PoolName $pName -Partition $Partition -Application $Application | Add-PoolMember -F5session $F5Session -Address $Address -Name $Name -Description $Description -PortNumber $PortNumber -Status $Status -Application $Application 113 | 114 | } 115 | } 116 | } 117 | } 118 | } -------------------------------------------------------------------------------- /F5-LTM/Private/Add-ObjectDetail.ps1: -------------------------------------------------------------------------------- 1 | function Add-ObjectDetail 2 | { 3 | <# 4 | .SYNOPSIS 5 | Decorate an object with 6 | - A TypeName 7 | - New properties 8 | - Default parameters 9 | 10 | .DESCRIPTION 11 | Helper function to decorate an object with 12 | - A TypeName 13 | - New properties 14 | - Default parameters 15 | 16 | .PARAMETER InputObject 17 | Object to decorate. Accepts pipeline input. 18 | 19 | .PARAMETER TypeName 20 | Typename to insert. 21 | 22 | This will show up when you use Get-Member against the resulting object. 23 | 24 | .PARAMETER PropertyToAdd 25 | Add these noteproperties. 26 | 27 | Format is a hashtable with Key (Property Name) = Value (Property Value). 28 | 29 | Example to add a One and Date property: 30 | 31 | -PropertyToAdd @{ 32 | One = 1 33 | Date = (Get-Date) 34 | } 35 | 36 | .PARAMETER DefaultProperties 37 | Change the default properties that show up 38 | 39 | .PARAMETER Passthru 40 | Whether to pass the resulting object on. Defaults to true 41 | 42 | .EXAMPLE 43 | # 44 | # Create an object to work with 45 | $Object = [PSCustomObject]@{ 46 | First = 'Cookie' 47 | Last = 'Monster' 48 | Account = 'CMonster' 49 | } 50 | 51 | #Add a type name and a random property 52 | Add-ObjectDetail -InputObject $Object -TypeName 'ApplicationX.Account' -PropertyToAdd @{ AnotherProperty = 5 } 53 | 54 | # First Last Account AnotherProperty 55 | # ----- ---- ------- --------------- 56 | # Cookie Monster CMonster 5 57 | 58 | #Verify that get-member shows us the right type 59 | $Object | Get-Member 60 | 61 | # TypeName: ApplicationX.Account ... 62 | 63 | .EXAMPLE 64 | # 65 | # Create an object to work with 66 | $Object = [PSCustomObject]@{ 67 | First = 'Cookie' 68 | Last = 'Monster' 69 | Account = 'CMonster' 70 | } 71 | 72 | #Add a random property, set a default property set so we only see two props by default 73 | Add-ObjectDetail -InputObject $Object -PropertyToAdd @{ AnotherProperty = 5 } -DefaultProperties Account, AnotherProperty 74 | 75 | # Account AnotherProperty 76 | # ------- --------------- 77 | # CMonster 5 78 | 79 | #Verify that the other properties are around 80 | $Object | Select -Property * 81 | 82 | # First Last Account AnotherProperty 83 | # ----- ---- ------- --------------- 84 | # Cookie Monster CMonster 5 85 | 86 | .NOTES 87 | This breaks the 'do one thing' rule from certain perspectives... 88 | The goal is to decorate an object all in one shot 89 | 90 | This abstraction simplifies decorating an object, with a slight trade-off in performance. For example: 91 | 92 | 10,000 objects, add a property and typename: 93 | Add-ObjectDetail: ~4.6 seconds 94 | Add-Member + PSObject.TypeNames.Insert: ~3 seconds 95 | 96 | Initial code borrowed from Shay Levy: 97 | http://blogs.microsoft.co.il/scriptfanatic/2012/04/13/custom-objects-default-display-in-powershell-30/ 98 | .LINK 99 | https://raw.githubusercontent.com/RamblingCookieMonster/PSStash/master/PSStash/Private/Add-ObjectDetail.ps1 100 | #> 101 | [cmdletBinding()] 102 | param( 103 | [Parameter( Mandatory = $false, 104 | Position=0, 105 | ValueFromPipeline=$true )] 106 | [psobject[]]$InputObject, 107 | 108 | [Parameter( Mandatory = $false, 109 | Position=1)] 110 | [string]$TypeName, 111 | 112 | [Parameter( Mandatory = $false, 113 | Position=2)] 114 | [System.Collections.Hashtable]$PropertyToAdd, 115 | 116 | [Parameter( Mandatory = $false, 117 | Position=3)] 118 | [ValidateNotNullOrEmpty()] 119 | [Alias('dp')] 120 | [System.String[]]$DefaultProperties, 121 | 122 | [boolean]$Passthru = $True 123 | ) 124 | 125 | Begin 126 | { 127 | if($PSBoundParameters.ContainsKey('DefaultProperties')) 128 | { 129 | # define a subset of properties 130 | $ddps = New-Object System.Management.Automation.PSPropertySet DefaultDisplayPropertySet,$DefaultProperties 131 | $PSStandardMembers = [System.Management.Automation.PSMemberInfo[]]$ddps 132 | } 133 | } 134 | Process 135 | { 136 | foreach($Object in $InputObject) 137 | { 138 | switch ($PSBoundParameters.Keys) 139 | { 140 | 'PropertyToAdd' 141 | { 142 | foreach($Key in $PropertyToAdd.Keys) 143 | { 144 | #Add some noteproperties. Slightly faster than Add-Member. 145 | $Object.PSObject.Properties.Add( ( New-Object PSNoteProperty($Key, $PropertyToAdd[$Key]) ) ) 146 | } 147 | } 148 | 'TypeName' 149 | { 150 | #Add specified type 151 | [void]$Object.PSObject.TypeNames.Insert(0,$TypeName) 152 | } 153 | 'DefaultProperties' 154 | { 155 | # Attach default display property set 156 | Add-Member -InputObject $Object -MemberType MemberSet -Name PSStandardMembers -Value $PSStandardMembers 157 | } 158 | } 159 | if($Passthru) 160 | { 161 | $Object 162 | } 163 | } 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /F5-LTM/Public/New-ProfileHttp.ps1: -------------------------------------------------------------------------------- 1 | Function New-ProfileHttp { 2 | <# 3 | .SYNOPSIS 4 | Create a new profile. 5 | 6 | .DESCRIPTION 7 | Read more about http profiles 8 | https://devcentral.f5.com/Wiki/iControlREST.APIRef_tm_ltm_profile_http.ashx 9 | 10 | .EXAMPLE 11 | New-Profile -F5Session $F5Session -Name $ProfileName -Partition $Partition -insertXforwardedFor "Enabled" 12 | .EXAMPLE 13 | New-Profile -F5Session $F5Session -Name "Service1" -Partition "ADO" -insertXforwardedFor "Enabled" 14 | .EXAMPLE 15 | Result of RestAPI 16 | kind : tm:ltm:profile:http:httpstate 17 | name : http_ServiceGB123 18 | partition : ADO 19 | fullPath : /ADO/http_ServiceGB123 20 | generation : 7645 21 | selfLink : https://localhost/mgmt/tm/ltm/profile/http/~ADO 22 | acceptXff : Disabled 23 | appService : None 24 | basicAuthRealm : None 25 | defaultsFrom : /Common/http 26 | defaultsFromReference : @{link=https://localhost/mgmt/tm/ltm/profile/ht 27 | description : None 28 | encryptCookieSecret : **** 29 | encryptCookies : {} 30 | enforcement : @{excessClientHeaders=reject; excessServerHeade 31 | truncatedRedirects=Disabled; unknownMethod=Allo 32 | explicitProxy : @{badRequestMessage=None; badResponseMessage=no 33 | fallbackHost : None 34 | fallbackStatusCodes : {} 35 | headerErase : None 36 | headerInsert : None 37 | hsts : @{includeSubdomains=Enabled; maximumAge=1607040 38 | insertXforwardedFor : Enabled 39 | lwsSeparator : None 40 | lwsWidth : 80 41 | oneconnectTransformations : Enabled 42 | proxyType : reverse 43 | redirectRewrite : None 44 | requestChunking : Preserve 45 | responseChunking : Selective 46 | responseHeadersPermitted : {} 47 | serverAgentName : BigIP 48 | sflow : @{pollInterval=0; pollIntervalGlobal=yes; sampl 49 | viaHostName : None 50 | viaRequest : Preserve 51 | viaResponse : Preserve 52 | xffAlternativeNames : {} 53 | #> 54 | [cmdletbinding()] 55 | param ( 56 | $F5Session=$Script:F5Session, 57 | [Alias('ProfileName')] 58 | [Parameter(Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] 59 | [string[]]$Name, 60 | 61 | [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)] 62 | [string]$Partition, 63 | [ValidateSet('enabled','disabled')] 64 | [string]$acceptXff, 65 | [string]$appService, 66 | [string]$basicAuthRealm, 67 | [string]$description, 68 | [string]$encryptCookieSecret, 69 | [string[]]$encryptCookies=@(), 70 | [string]$fallbackHost, 71 | [string[]]$fallbackStatusCodes=@(), 72 | [string]$headerErase, 73 | [string]$headerInsert, 74 | [ValidateSet('Enabled','Disabled')] 75 | [string]$insertXforwardedFor, 76 | [string]$lwsSeparator, 77 | [int]$lwsWidth, 78 | [string]$oneconnectTransformations, 79 | [string]$tmPartition, 80 | [string]$proxyType, 81 | [ValidateSet('None','All','Matching','Nodes')] 82 | [string]$redirectRewrite, 83 | [ValidateSet('Preserve','Selective','Rechunk')] 84 | [string]$requestChunking, 85 | [ValidateSet('Preserve','Selective','Unchunk','Rechunk')] 86 | [string]$responseChunking, 87 | [string]$responseHeadersPermitted, 88 | [string]$serverAgentName, 89 | [string]$viaHostName, 90 | [ValidateSet('Remove','Preserve','Append')] 91 | [string]$viaRequest, 92 | [ValidateSet('Remove','Preserve','Append')] 93 | [string]$viaResponse, 94 | [string]$xffAlternativeNames, 95 | [string]$Enforcement 96 | ) 97 | begin { 98 | #Test that the F5 session is in a valid format 99 | Test-F5Session($F5Session) 100 | 101 | 102 | } 103 | process { 104 | $URI = ($F5Session.BaseURL + "profile/http") 105 | foreach ($profilename in $Name) { 106 | $newitem = New-F5Item -Name $profilename -Partition $Partition 107 | #Check whether the specified profile already exists 108 | If (Test-Profilehttp -F5session $F5Session -Name $newitem.Name -Partition $newitem.Partition){ 109 | Write-Error "The $($newitem.FullPath) profile already exists." 110 | } 111 | Else { 112 | #Start building the JSON for the action 113 | if($null -eq $Enforcement){$Enforcement = @{}} 114 | $JSONBody = @{name=$newitem.Name;partition=$newitem.Partition;acceptXff=$acceptXff;appService=$appService;basicAuthRealm=$basicAuthRealm;defaultsFrom="/Common/http";description=$description;encryptCookieSecret=$encryptCookieSecret;encryptCookies=$encryptCookies;fallbackHost=$fallbackHost;fallbackStatusCodes=$fallbackStatusCodes;headerErase=$headerErase;headerInsert=$headerInsert;insertXforwardedFor=$insertXforwardedFor;lwsSeparator=$lwsSeparator;lwsWidth=$lwsWidth;oneconnectTransformations=$oneconnectTransformations;tmPartition=$tmPartition;proxyType=$proxyType;redirectRewrite=$redirectRewrite;requestChunking=$requestChunking;responseChunking=$responseChunking;responseHeadersPermitted=$responseHeadersPermitted;serverAgentName=$serverAgentName;viaHostName=$viaHostName;viaRequest=$viaRequest;viaResponse=$viaResponse;xffAlternativeNames=$xffAlternativeNames;Enforcement=$Enforcement} 115 | ($JSONBody.GetEnumerator() | Where-Object Value -eq "" ).name | ForEach-Object {$JSONBody.Remove($_)} 116 | $JSONBody = $JSONBody | ConvertTo-Json 117 | Invoke-F5RestMethod -Method POST -Uri "$URI" -F5Session $F5Session -Body $JSONBody -ContentType 'application/json' -ErrorMessage ("Failed to create the $($newitem.FullPath) profile.") -AsBoolean 118 | Write-Verbose "If viaRequest or viaResponse is set to 'append,' then a value for viaHostName is required." 119 | 120 | } 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /F5-LTM/Public/New-F5Session.ps1: -------------------------------------------------------------------------------- 1 | Function New-F5Session{ 2 | <# 3 | .SYNOPSIS 4 | Generate an F5 session object to be used in querying and modifying the F5 LTM 5 | .DESCRIPTION 6 | This function takes the DNS name or IP address of the F5 LTM device, and a PSCredential credential object 7 | for a user with permissions to work with the REST API. Based on the scope value, it either returns the 8 | session object (local scope) or adds the session object to the script scope 9 | It takes an optional parameter of TokenLifespan, a value in seconds between 300 and 36000 (5 minutes and 10 hours). 10 | NB: If you need to connect to an LTM on other than the standard HTTPS port of 443, please include that port in the LTM name. I.e. $LTMName = '192.168.1.1:8443' 11 | #> 12 | [cmdletBinding()] 13 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")] 14 | param( 15 | [Parameter(Mandatory=$true)][string]$LTMName, 16 | [Parameter(Mandatory=$true)][System.Management.Automation.PSCredential]$LTMCredentials, 17 | [switch]$Default, 18 | [Alias('PassThrough')] 19 | [switch]$PassThru, 20 | [ValidateRange(300,36000)][int]$TokenLifespan=1200 21 | ) 22 | If ($PSVersionTable.PSVersion -lt '7.2') { 23 | $OriginalWarningPreference = $WarningPreference 24 | If ($PSBoundParameters.ContainsKey('WarningAction')) { 25 | $WarningPreference = $PSBoundParameters['WarningAction'] 26 | } else { 27 | $WarningPreference = 'SilentlyContinue' 28 | } 29 | } 30 | $BaseURL = "https://$LTMName/mgmt/tm/ltm/" 31 | 32 | $session = New-Object Microsoft.PowerShell.Commands.WebRequestSession 33 | 34 | #First, we attempt to get an authorization token. We need an auth token to do anything for LTMs using external authentication, including getting the LTM version. 35 | #If we fail to get an auth token, that means the LTM version is prior to 11.6, so we fall back on Credentials 36 | $AuthURL = "https://$LTMName/mgmt/shared/authn/login" 37 | $JSONBody = @{username = $LTMCredentials.username; password=$LTMCredentials.GetNetworkCredential().password; loginProviderName='tmos'} | ConvertTo-Json -WarningAction $WarningPreference 38 | 39 | 40 | try { 41 | $Result = Invoke-RestMethodOverride -Method POST -Uri $AuthURL -Body $JSONBody -Credential $LTMCredentials -ContentType 'application/json' 42 | 43 | $Token = $Result.token.token 44 | $session.Headers.Add('X-F5-Auth-Token', $Token) 45 | 46 | #A UUID is returned by LTM v11.6. This is needed for modifying the token. 47 | #For v12+, the name value is used. 48 | If ($Result.token.uuid){ 49 | $TokenReference = $Result.token.uuid; 50 | } 51 | Else { 52 | $TokenReference = $Result.token.name; 53 | } 54 | 55 | #If a value for TokenLifespan was passed in, then patch the token with this expiration value 56 | #Max value is 36000 seconds (10 hours) 57 | If ($TokenLifespan -ne 1200){ 58 | 59 | $Body = @{ timeout = $TokenLifespan } | ConvertTo-Json -WarningAction $WarningPreference 60 | $Headers = @{ 61 | 'X-F5-Auth-Token' = $Token 62 | } 63 | 64 | Invoke-RestMethodOverride -Method Patch -Uri https://$LTMName/mgmt/shared/authz/tokens/$TokenReference -Headers $Headers -Body $Body -WebSession $session | Out-Null 65 | 66 | } 67 | 68 | # Add token expiration time to session 69 | $ts = New-TimeSpan -Minutes ($TokenLifespan/60) 70 | $date = Get-Date -Date $Result.token.startTime 71 | $ExpirationTime = $date + $ts 72 | $session.Headers.Add('Token-Expiration', $ExpirationTime) 73 | 74 | } catch { 75 | # We failed to retrieve an authorization token. Either the version of the LTM is pre 11.6, or the $LTMName is not valid 76 | # Verify that the LTM base URL is available. Otherwise return a message saying the LTM specified is not valid. 77 | Try { 78 | Invoke-WebRequest -Uri $BaseURL -ErrorVariable LTMError -TimeoutSec 5 -NoProxy 79 | } 80 | Catch { 81 | #If an error is thrown and it doesn't contain the word 'Unauthorized' then the LTM name and $BaseURL are invalid 82 | If ($LTMError[0] -notmatch 'Unauthorized'){ 83 | $LTMError 84 | Throw ("The specified LTM name $LTMName is not valid.") 85 | } 86 | } 87 | # fall back to Credentials 88 | Write-Verbose "The version must be prior to 11.6 since we failed to retrieve an auth token." 89 | $Credential = $LTMCredentials 90 | } 91 | 92 | $newSession = [pscustomobject]@{ 93 | Name = $LTMName 94 | BaseURL = $BaseURL 95 | Credential = $Credential 96 | WebSession = $session 97 | Token = $Token 98 | } | Add-Member -Name GetLink -MemberType ScriptMethod { 99 | param($Link) 100 | $Link -replace 'localhost', $this.Name 101 | } -PassThru 102 | 103 | If ($PSVersionTable.PSVersion -lt '7.2') { 104 | $WarningPreference = $OriginalWarningPreference 105 | } 106 | 107 | # Since we've connected to the LTM, we can now retrieve the device version 108 | # We'll add it to the session object and reference it in cases where the iControlREST web services differ between LTM versions. 109 | $VersionURL = $BaseURL.Replace('ltm/','sys/version/') 110 | $JSON = Invoke-F5RestMethod -Method Get -Uri $VersionURL -F5Session $newSession | ConvertTo-Json -WarningAction $WarningPreference -Depth 10 111 | 112 | $version = '0.0.0.0' # Default value, rather than throw error 113 | if ($JSON -match '(\d+\.?){3,4}') { 114 | $version = [Regex]::Match($JSON,'(\d+\.?){3,4}').Value 115 | } 116 | $newSession | Add-Member -Name LTMVersion -Value ([Version]$version) -MemberType NoteProperty 117 | 118 | #If the Default switch is set, and/or if no script-scoped F5Session exists, then set the script-scoped F5Session 119 | If ($Default -or !($Script:F5Session)){ 120 | $Script:F5Session = $newSession 121 | } 122 | 123 | #If the Passthrough switch is set, then return the created F5Session object. 124 | If ($PassThru){ 125 | $newSession 126 | } 127 | } -------------------------------------------------------------------------------- /F5-LTM/Public/New-VirtualServer.ps1: -------------------------------------------------------------------------------- 1 | Function New-VirtualServer 2 | { 3 | <# 4 | .SYNOPSIS 5 | Create a new virtual server 6 | #> 7 | [cmdletbinding(DefaultParameterSetName="VlanEnabled")] 8 | 9 | param ( 10 | $F5Session = $Script:F5Session 11 | , 12 | [Parameter(Mandatory = $false)]$Kind = 'tm:ltm:virtual:virtualstate' 13 | , 14 | [Parameter(Mandatory = $True)] 15 | [Alias('VirtualServerName')] 16 | [string]$Name 17 | , 18 | 19 | [Alias('iApp')] 20 | [Parameter(Mandatory=$false)] 21 | [string]$Application='', 22 | 23 | [Parameter(Mandatory = $false)] 24 | [string]$Partition 25 | , 26 | [Parameter(Mandatory = $false)] 27 | $Description = $null 28 | , 29 | [Parameter(Mandatory = $True)] 30 | [PoshLTM.F5Address]$DestinationIP 31 | , 32 | [Parameter(Mandatory = $True)] 33 | $DestinationPort 34 | , 35 | [Parameter(Mandatory = $false, ParameterSetName = 'VlanEnabled')] 36 | [string[]]$VlanEnabled 37 | , 38 | [Parameter(Mandatory = $false, ParameterSetName = 'VlanDisabled')] 39 | [string[]]$VlanDisabled 40 | , 41 | [Parameter(Mandatory = $false)] 42 | $Source = '0.0.0.0/0' 43 | , 44 | [Parameter(Mandatory = $false)] 45 | $DefaultPool = $null 46 | , 47 | [Parameter(Mandatory = $false)] 48 | [string[]]$ProfileNames = $null 49 | , 50 | [Parameter(Mandatory = $false)] 51 | [string[]]$Rules = $null 52 | , 53 | [Parameter(Mandatory = $True)] 54 | [ValidateSet('tcp','udp','sctp')] 55 | $ipProtocol = $null 56 | , 57 | [Parameter(Mandatory = $false)] 58 | $Mask = '255.255.255.255' 59 | , 60 | [Parameter(Mandatory = $false)] 61 | $ConnectionLimit = '0' 62 | , 63 | [Parameter(Mandatory = $false)] 64 | [ValidateSet('true','false')] 65 | $Enabled = 'true' 66 | , 67 | [Parameter(Mandatory = $false)] 68 | [ValidateSet('automap','snat','none')] 69 | $SourceAddressTranslationType 70 | , 71 | [Parameter(Mandatory = $false)] 72 | [string]$SourceAddressTranslationPool 73 | , 74 | [Parameter(Mandatory = $false)] 75 | [string[]]$PersistenceProfiles 76 | , 77 | [Parameter(Mandatory = $false)] 78 | [string]$FallbackPersistence 79 | , 80 | [Parameter(Mandatory = $false)] 81 | [string[]]$SecurityLogProfiles 82 | , 83 | [Parameter(Mandatory = $false)] 84 | [string[]]$PolicyNames = $null 85 | 86 | 87 | ) 88 | 89 | #Test that the F5 session is in a valid format 90 | Test-F5Session($F5Session) 91 | 92 | $URI = ($F5Session.BaseURL + 'virtual') 93 | 94 | #Check whether the specified virtual server already exists 95 | If (Test-VirtualServer -F5session $F5Session -Name $Name) 96 | { 97 | Write-Error -Message "The $Name virtual server already exists." 98 | } 99 | Else 100 | { 101 | $newitem = New-F5Item -Name $Name -Application $Application -Partition $Partition 102 | 103 | #Start building the JSON for the action 104 | $Destination = $DestinationIP.ToString() + ':' + $DestinationPort 105 | $JSONBody = @{ 106 | kind = $Kind 107 | name = $newitem.Name 108 | description = $Description 109 | partition = $newitem.Partition 110 | destination = $Destination 111 | source = $Source 112 | pool = $DefaultPool 113 | ipProtocol = $ipProtocol 114 | mask = $Mask 115 | rules = $rules 116 | connectionLimit = $ConnectionLimit 117 | fallbackPersistence = $FallbackPersistence 118 | 119 | } 120 | 121 | if ($PersistenceProfiles) 122 | { 123 | $JSONBody.persist = $PersistenceProfiles 124 | } 125 | if ($SecurityLogProfiles) 126 | { 127 | $JSONBody.securityLogProfiles = $SecurityLogProfiles 128 | } 129 | 130 | if ($newItem.application) { 131 | $JSONBody.Add('application',$newItem.application) 132 | } 133 | 134 | #Extra options for Vlan handling. Sets Vlans for VirtualServer, and sets it to be en- or disabled on those Vlans. 135 | If ($VlanEnabled) 136 | { 137 | $JSONBody.vlans = $VlanEnabled 138 | $JSONBody.vlansEnabled = $True 139 | } 140 | elseif ($VlanDisabled) 141 | { 142 | $JSONBody.vlans = $VlanDisabled 143 | $JSONBody.vlansDisabled = $True 144 | } 145 | 146 | if ($Enabled -eq 'true'){ 147 | $JSONBody.enabled = $True 148 | } 149 | elseif ($Enabled -eq 'false'){ 150 | $JSONBody.disabled = $True 151 | } 152 | 153 | #Settings for source address translation 154 | If ($SourceAddressTranslationType){ 155 | $SourceAddressTranslation = @{ 156 | type = $SourceAddressTranslationType 157 | } 158 | #If SourceAddressTranslationType is SNAT, then a value for sourceAddressTranslationPool is expected 159 | if ($SourceAddressTranslationType -eq 'snat'){ 160 | $SourceAddressTranslation.pool = $SourceAddressTranslationPool 161 | } 162 | } 163 | $JSONBody.sourceAddressTranslation = $SourceAddressTranslation 164 | 165 | #Build array of profile items 166 | $ProfileItems = @() 167 | ForEach ($ProfileName in $ProfileNames) 168 | { 169 | $ProfileItems += @{ 170 | kind = 'tm:ltm:virtual:profiles:profilesstate' 171 | name = $ProfileName 172 | } 173 | } 174 | $JSONBody.profiles = $ProfileItems 175 | 176 | #Build array of policy items 177 | $PolicyItems = @() 178 | ForEach ($PolicyName in $PolicyNames) 179 | { 180 | $PolicyItems += @{ 181 | kind = 'tm:ltm:virtual:policies:policiesstate' 182 | name = $PolicyName 183 | } 184 | } 185 | if ($PolicyItems.Count -gt 0) 186 | { 187 | $JSONBody.policies = $PolicyItems 188 | } 189 | 190 | $JSONBody = $JSONBody | ConvertTo-Json 191 | 192 | if ($pscmdlet.ShouldProcess($F5Session.Name, "Creating virtualserver $Name")) 193 | { 194 | Invoke-F5RestMethod -Method POST -Uri "$URI" ` 195 | -F5Session $F5Session ` 196 | -Body $JSONBody ` 197 | -ContentType 'application/json' ` 198 | -ErrorMessage "Failed to create the $($newitem.FullPath) virtual server." 199 | } 200 | } 201 | } -------------------------------------------------------------------------------- /F5-LTM/Public/Test-Functionality.ps1: -------------------------------------------------------------------------------- 1 | Function Test-Functionality{ 2 | <# 3 | .SYNOPSIS 4 | Perform some standard tests to make sure things work as expected 5 | .EXAMPLE 6 | Test-Functionality -F5Session $F5session -TestVirtualServer 'virt123' -TestVirtualServerIP '10.1.1.240' -TestPool 'testpool123' -PoolMemberAddress '10.1.1.100' 7 | #> 8 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingWriteHost", "")] 9 | param ( 10 | $F5Session=$Script:F5Session, 11 | [Parameter(Mandatory=$true)]$TestVirtualServer, 12 | [Parameter(Mandatory=$true)]$TestVirtualServerIP, 13 | [Parameter(Mandatory=$true)]$TestPool, 14 | [Parameter(Mandatory=$true)]$PoolMemberAddress 15 | ) 16 | 17 | $TestNotesColor = 'Cyan' 18 | 19 | Write-Host "-----`r`nBeginning test`r`n-----" -ForegroundColor $TestNotesColor 20 | 21 | Write-Host "* Get the failover status of the F5 device" -ForegroundColor $TestNotesColor 22 | Get-F5Status -F5Session $F5Session 23 | 24 | Write-Host "`r`n* Get a list of all pools" -ForegroundColor $TestNotesColor 25 | $pools = Get-Pool -F5Session $F5Session | Select-Object -ExpandProperty fullPath 26 | $pools 27 | 28 | #Get the first pool. If there is only one, don't treat it as an array 29 | If ($pools -is [system.array]){ 30 | $FirstPool = $pools[0]; 31 | } 32 | Else { 33 | $FirstPool = $pools 34 | } 35 | 36 | Write-Host ("`r`n* Test whether the first pool in the list - " + $FirstPool + " - exists") -ForegroundColor $TestNotesColor 37 | Test-Pool -F5Session $F5Session -PoolName $FirstPool 38 | 39 | Write-Host ("`r`n* Get the pool " + $FirstPool) -ForegroundColor $TestNotesColor 40 | Get-Pool -F5Session $F5Session -PoolName $FirstPool 41 | 42 | Write-Host ("`r`n* Get members of the pool '" + $FirstPool + "'") -ForegroundColor $TestNotesColor 43 | Get-PoolMember -F5Session $F5Session -PoolName $FirstPool 44 | 45 | Write-Host ("`r`n* Get the status of all members in the " + $FirstPool + " pool") -ForegroundColor $TestNotesColor 46 | Get-PoolMember -F5Session $F5Session -PoolName $FirstPool | Select-Object -Property name,session,state 47 | 48 | Write-Host "`r`n* Create a new pool named '$TestPool'" -ForegroundColor $TestNotesColor 49 | New-Pool -F5Session $F5Session -PoolName $TestPool -LoadBalancingMode dynamic-ratio-member 50 | 51 | Write-Host "`r`n* Add the computer $PoolMember to the pool '$TestPool'" -ForegroundColor $TestNotesColor 52 | Add-PoolMember -F5Session $F5Session -Address $PoolMemberAddress -PortNumber 80 -PoolName $TestPool -Status Enabled 53 | 54 | Write-Host "`r`n* Get the new pool member" -ForegroundColor $TestNotesColor 55 | Get-PoolMember -F5Session $F5Session -Address $PoolMemberAddress -PoolName $TestPool 56 | 57 | Write-Host "`r`n* Get the IP address for the new pool member" -ForegroundColor $TestNotesColor 58 | Get-PoolMember -F5Session $F5Session -Address $PoolMemberAddress -PoolName $TestPool | Select-Object -ExpandProperty address 59 | 60 | Write-Host "`r`n* Get all pools of which this pool member is a member" -ForegroundColor $TestNotesColor 61 | Get-PoolsForMember -F5Session $F5Session -Address $PoolMemberAddress 62 | 63 | Write-Host "`r`n* Get the number of current connections for this pool member" -ForegroundColor $TestNotesColor 64 | Get-PoolMemberStats -F5Session $F5Session -Address $PoolMemberAddress -PoolName $TestPool | Select-Object -ExpandProperty 'serverside.curConns' 65 | 66 | Write-Host "`r`n* Disable the new pool member" -ForegroundColor $TestNotesColor 67 | Disable-PoolMember -F5Session $F5Session -Address $PoolMemberAddress -PoolName $TestPool 68 | 69 | Write-Host "`r`n* Get the status of the new pool member" -ForegroundColor $TestNotesColor 70 | $PoolMemberStatus = Get-PoolMember -F5Session $F5Session -Address $PoolMemberAddress -PoolName $TestPool | Select-Object -Property name,session,state 71 | $PoolMemberStatus 72 | 73 | Write-Host "`r`n* Set the pool member description to 'My new pool' and retrieve it" -ForegroundColor $TestNotesColor 74 | Write-Host "Old description:" 75 | #NB: If there is not description for the pool member, no Description propery is returned. 76 | Get-PoolMember -F5Session $F5Session -Address $PoolMemberAddress -PoolName $TestPool | Select-Object -ExpandProperty Description -ErrorAction SilentlyContinue 77 | 78 | Set-PoolMemberDescription -F5Session $F5Session -Address $PoolMemberAddress -PoolName $TestPool -Description 'My new pool' | out-null 79 | Write-Host "New description:" 80 | Get-PoolMember -F5Session $F5Session -Address $PoolMemberAddress -PoolName $TestPool | Select-Object -ExpandProperty Description 81 | 82 | Write-Host "`r`n* Enable the new pool member" -ForegroundColor $TestNotesColor 83 | Enable-PoolMember -F5Session $F5Session -Address $PoolMemberAddress -PoolName $TestPool 84 | 85 | Write-Host "`r`n* Remove the new pool member from the pool" -ForegroundColor $TestNotesColor 86 | Remove-PoolMember -F5Session $F5Session -Address $PoolMemberAddress -PoolName $TestPool 87 | 88 | Write-Host "`r`n* Get a list of all virtual servers" -ForegroundColor $TestNotesColor 89 | $virtualServers = Get-VirtualServer -F5Session $F5Session | Select-Object -ExpandProperty fullPath 90 | $virtualServers 91 | 92 | If ($virtualServers -is [array]){ 93 | $firstVirtualServer = $virtualServers[0] 94 | } 95 | Else { 96 | $firstVirtualServer = $virtualServers 97 | } 98 | 99 | Write-Host ("`r`n* Test whether the first virtual server in the list - " + $firstVirtualServer + " - exists") -ForegroundColor $TestNotesColor 100 | Test-VirtualServer -F5Session $F5Session -VirtualServerName $firstVirtualServer 101 | 102 | Write-Host ("`r`n* Get the virtual server '" + $firstVirtualServer + "'") -ForegroundColor $TestNotesColor 103 | Get-VirtualServer -F5Session $F5Session -VirtualServerName $firstVirtualServer 104 | 105 | Write-Host "`r`n* Create a new virtual server named '$TestVirtualServer'" -ForegroundColor $TestNotesColor 106 | New-VirtualServer -F5Session $F5Session -VirtualServerName $TestVirtualServer -Description 'description' -DestinationIP $TestVirtualServerIP -DestinationPort '80' -DefaultPool $TestPool -IPProtocol 'tcp' -ProfileNames 'http' 107 | 108 | Write-Host ("`r`n* Retrieve all iRules on the F5 LTM device.") -ForegroundColor $TestNotesColor 109 | $iRules = Get-iRule -F5Session $F5Session 110 | Write-Output ("- This can be a large collection. The first entry found is:") 111 | Write-Output $iRules[0] 112 | 113 | Write-Host ("`r`n* Add the iRule '_sys_https_redirect' to the new virtual server '$TestVirtualServer'") -ForegroundColor $TestNotesColor 114 | Add-iRuleToVirtualServer -F5Session $F5Session -VirtualServer $TestVirtualServer -iRuleName '_sys_https_redirect' 115 | 116 | Write-Host "`r`n* Get all iRules assigned to '$TestVirtualServer'" -ForegroundColor $TestNotesColor 117 | Get-VirtualServer -F5Session $F5Session -VirtualServer $TestVirtualServer | Select-Object -ExpandProperty rules 118 | 119 | Write-Host ("`r`n* Remove the '_sys_https_redirect' iRule from the new virtual server '$TestVirtualServer'") -ForegroundColor $TestNotesColor 120 | Remove-iRuleFromVirtualServer -F5Session $F5Session -VirtualServer $TestVirtualServer -iRuleName '_sys_https_redirect' 121 | 122 | Write-Host "`r`n* Remove the new virtual server '$TestVirtualServer'" -ForegroundColor $TestNotesColor 123 | Write-Host "(This will raise a confirmation prompt unless -confirm is set to false)" -ForegroundColor $TestNotesColor 124 | Remove-VirtualServer -F5Session $F5Session -VirtualServerName $TestVirtualServer 125 | 126 | Write-Host "`r`n* Remove the new pool '$TestPool'" -ForegroundColor $TestNotesColor 127 | Write-Host "(This will raise a confirmation prompt unless -confirm is set to false)" -ForegroundColor $TestNotesColor 128 | Remove-Pool -F5Session $F5Session -PoolName $TestPool 129 | 130 | Write-Host "-----`r`nTest complete`r`n-----" -ForegroundColor $TestNotesColor 131 | 132 | } -------------------------------------------------------------------------------- /F5-LTM/Public/Set-Pool.ps1: -------------------------------------------------------------------------------- 1 | Function Set-Pool { 2 | <# 3 | .SYNOPSIS 4 | Create or update Pool(s) 5 | .DESCRIPTION 6 | Can create new or update existing Pool(s). 7 | .PARAMETER InputObject 8 | The content of the Pool. 9 | .PARAMETER Application 10 | The iApp of the Pool. 11 | .PARAMETER Partition 12 | The partition on the F5 to put the Pool on. 13 | .PARAMETER PassThru 14 | Output the modified Pool to the pipeline. 15 | .EXAMPLE 16 | Set-Pool -Name 'northwindtraders_servers' -Description 'Northwind Traders example' -LoadBalancingMode dynamic-ratio-member 17 | 18 | Creates or updates a Pool. Note that parameters that are Mandatory for New-Pool (Name and LoadBalancingMode) must be specified for Pools that do not yet exist. 19 | 20 | .EXAMPLE 21 | Set-Pool -Name 'northwindtraders_servers' -Description 'Some useful description' 22 | 23 | Sets the description of an existing Pool. 24 | 25 | .EXAMPLE 26 | Set-Pool -Name 'northwindtraders_servers' -MemberDefinitionList {192.168.1.100,80},{192.168.1.101,80} 27 | 28 | .EXAMPLE 29 | $pool = Get-Pool -Name 'northwindtraders_servers'; 30 | $pool.minActiveMembers = if ($pool.minActiveMembers -lt 3) { 3 }; 31 | $pool | Set-Pool -PassThru; 32 | 33 | Set the minimum active pool members to 3 if currently less than 3 and returns the resulting Pool with -PassThru. 34 | 35 | #> 36 | [cmdletbinding(ConfirmImpact='Medium',SupportsShouldProcess,DefaultParameterSetName="Default")] 37 | param ( 38 | $F5Session=$Script:F5Session, 39 | 40 | [Parameter(Mandatory,ParameterSetName='InputObject',ValueFromPipeline)] 41 | [Alias('Pool')] 42 | [PSObject[]]$InputObject, 43 | 44 | #region Immutable fullPath component params 45 | 46 | [Alias('PoolName')] 47 | [Parameter(Mandatory,ValueFromPipelineByPropertyName)] 48 | $Name, 49 | 50 | [Alias('iApp')] 51 | [Parameter(ValueFromPipelineByPropertyName)] 52 | $Application='', 53 | 54 | [Parameter(ValueFromPipelineByPropertyName)] 55 | $Partition='Common', 56 | 57 | #endregion 58 | 59 | #region New-Pool equivalents 60 | 61 | [string]$Description, 62 | 63 | [ValidateSet('dynamic-ratio-member','dynamic-ratio-node','fastest-app-response','fastest-node','least-connections-member','least-connections-node','least-sessions','observed-member','observed-node','predictive-member','predictive-node','ratio-least-connections-member','ratio-least-connections-node','ratio-member','ratio-node','ratio-session','round-robin','weighted-least-connections-member','weighted-least-connections-node')] 64 | [string]$LoadBalancingMode, 65 | 66 | [string[]]$MemberDefinitionList, 67 | 68 | #endregion 69 | 70 | [switch]$PassThru 71 | ) 72 | 73 | begin { 74 | Test-F5Session -F5Session ($F5Session) 75 | 76 | Write-Verbose "NB: Pool names are case-specific." 77 | 78 | $knownproperties = @{ 79 | name='name' 80 | partition='partition' 81 | kind='kind' 82 | description='description' 83 | loadBalancingMode='loadBalancingMode' 84 | membersReference='membersReference' 85 | monitor='monitor' 86 | } 87 | } 88 | 89 | process { 90 | if ($InputObject -and ( 91 | ($Name -and $Name -cne $InputObject.name) -or 92 | ($Partition -and $Partition -cne $InputObject.partition) -or 93 | ($Application -and $Application -cne $InputObject.application) 94 | ) 95 | ) { 96 | throw 'Set-Pool does not support moving or renaming at this time. Use New-Pool and Remove-Pool.' 97 | } 98 | 99 | $NewProperties = @{} # A hash table to facilitate splatting of New-Pool params 100 | $ChgProperties = @{} # A hash table of PSBoundParameters to override InputObject properties 101 | 102 | # Build out both hashtables based on $PSBoundParameters 103 | foreach ($key in $PSBoundParameters.Keys) { 104 | switch ($key) { 105 | 'InputObject' {} # Ignore 106 | 'PassThru' {} # Ignore 107 | { @('F5Session','MemberDefinitionList') -contains $key } { 108 | $NewProperties[$key] = $PSBoundParameters[$key] 109 | } 110 | default { 111 | if ($knownproperties.ContainsKey($key)) { 112 | $NewProperties[$key] = $ChgProperties[$knownproperties[$key]] = $PSBoundParameters[$key] 113 | } 114 | } 115 | } 116 | } 117 | 118 | $ExistingPool = Get-Pool -F5Session $F5Session -Name $Name -Application $Application -Partition $Partition -ErrorAction SilentlyContinue 119 | 120 | if ($null -eq $ExistingPool) { 121 | Write-Verbose -Message 'Creating new Pool...' 122 | $null = New-Pool @NewProperties 123 | } 124 | 125 | # This performs the magic necessary for ChgProperties to override $InputObject properties 126 | $NewObject = Join-Object -Left $InputObject -Right ([pscustomobject]$ChgProperties) -Join FULL -WarningAction SilentlyContinue 127 | if ($null -ne $NewObject -and $pscmdlet.ShouldProcess($F5Session.Name, "Setting Pool $Name")) { 128 | 129 | # We only update the pool if properties other than 'Name' are passed in 130 | If ($NewObject | Get-Member -MemberType NoteProperty | Where-Object Name -ne 'Name'){ 131 | 132 | Write-Verbose -Message 'Setting Pool details...' 133 | 134 | $URI = $F5Session.BaseURL + 'pool/{0}' -f (Get-ItemPath -Name $Name -Application $Application -Partition $Partition) 135 | $JSONBody = $NewObject | ConvertTo-Json -Compress 136 | 137 | #region case-sensitive parameter names 138 | 139 | # If someone inputs their own custom PSObject with properties with unexpected case, this will correct the case of known properties. 140 | # It could arguably be removed. If not removed, it should be refactored into a shared (Private) function for use by all Set-* functions in the module. 141 | $knownRegex = '(?<=")({0})(?=":)' -f ($knownproperties.Keys -join '|') 142 | # Use of regex.Replace with a callback is more efficient than multiple, separate replacements 143 | $JsonBody = [regex]::Replace($JSONBody,$knownRegex,{param($match) $knownproperties[$match.Value] }, [Text.RegularExpressions.RegexOptions]::IgnoreCase) 144 | 145 | #endregion 146 | 147 | $null = Invoke-F5RestMethod -Method PATCH -URI "$URI" -F5Session $F5Session -Body $JSONBody -ContentType 'application/json' 148 | 149 | } 150 | 151 | # MemberDefinitionList should trump existing members IFF there is an ExistingPool, otherwise New-Pool will take care of initializing the members. 152 | if ($MemberDefinitionList -and $ExistingPool) { 153 | 154 | Write-Verbose -Message 'Setting Pool members...' 155 | 156 | # Remove all existing pool members 157 | Get-PoolMember -F5Session $F5Session -PoolName $Name -Partition $Partition | Remove-PoolMember -F5Session $F5Session -Confirm:$false 158 | # Add requested pool members 159 | ForEach ($MemberDefinition in $MemberDefinitionList){ 160 | $Node,$PortNumber = $MemberDefinition -split ',' 161 | $null = Add-PoolMember -F5Session $F5Session -PoolName $Name -Partition $Partition -Address $Node -PortNumber $PortNumber -Status Enabled 162 | } 163 | } 164 | } 165 | if ($PassThru) { Get-Pool -F5Session $F5Session -Name $Name -Application $Application -Partition $Partition } 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /F5-LTM/Private/ArgumentCompletion.ps1: -------------------------------------------------------------------------------- 1 | # Retrieve ALL ~/Public/*.ps1 Command names 2 | $Script:F5LTMPublicCommands = Get-ChildItem -Path ($PSScriptRoot -replace 'Private','Public') -Filter '*.ps1' -Recurse | Select-Object -ExpandProperty BaseName 3 | function Get-F5Command { 4 | <# 5 | .SYNOPSIS 6 | (Get-Command -Module F5-LTM) adversely affects module import performance. 7 | This is a much faster alternative without resorting to static command names 8 | for Register-ArgumentCompleter -Command parameters. 9 | #> 10 | param( 11 | [Parameter(ValueFromPipeline=$true)] 12 | [string[]]$Filter 13 | ) 14 | process{ 15 | foreach ($f in $filter) { 16 | $Script:F5LTMPublicCommands -like $f 17 | } 18 | } 19 | } 20 | function Get-CompleteSession { 21 | param($boundSession) 22 | Invoke-NullCoalescing {$boundSession} {$Script:F5Session} 23 | } 24 | function CompleteMonitorName { 25 | param($wordToComplete, 26 | $fakeBoundParameters) 27 | $Session = Get-CompleteSession $fakeBoundParameters.F5Session 28 | if ($Session) { 29 | Get-HealthMonitor -F5Session $Session -Partition $fakeBoundParameters.Partition -Type $fakeBoundParameters.Type | Where-Object { $_.name -like "$wordToComplete*" } | ForEach-Object { 30 | if ($fakeBoundParameters.Partition) { 31 | $_.name 32 | } else { 33 | $_.fullPath 34 | } 35 | } 36 | } 37 | } 38 | function CompleteMonitorType { 39 | param($wordToComplete, 40 | $fakeBoundParameters) 41 | $Session = Get-CompleteSession $fakeBoundParameters.F5Session 42 | if ($Session) { 43 | Get-HealthMonitorType -F5Session $Session | Where-Object { $_ -like "$wordToComplete*" } 44 | } 45 | } 46 | function CompleteNodeAddress { 47 | param($wordToComplete, 48 | $fakeBoundParameters) 49 | $Session = Get-CompleteSession $fakeBoundParameters.F5Session 50 | if ($Session) { 51 | Get-Node -F5Session $Session -Partition $fakeBoundParameters.Partition | 52 | Where-Object { $_.address -like "$wordToComplete*" } | 53 | Select-Object -ExpandProperty address 54 | } 55 | } 56 | function CompleteNodeName { 57 | param($wordToComplete, 58 | $fakeBoundParameters) 59 | $Session = Get-CompleteSession $fakeBoundParameters.F5Session 60 | if ($Session) { 61 | Get-Node -F5Session $Session -Partition $fakeBoundParameters.Partition | Where-Object { $_.name -like "$wordToComplete*" } | ForEach-Object { 62 | if ($fakeBoundParameters.Partition) { 63 | $_.name 64 | } else { 65 | $_.fullPath 66 | } 67 | } 68 | } 69 | } 70 | function CompleteBIGIPPartition { 71 | param($wordToComplete, 72 | $fakeBoundParameters) 73 | $Session = Get-CompleteSession $fakeBoundParameters.F5Session 74 | if ($Session) { 75 | Get-BIGIPPartition -F5Session $Session | Where-Object { $_ -like "$wordToComplete*" } 76 | } 77 | } 78 | function CompletePoolMemberAddress { 79 | param($wordToComplete, 80 | $fakeBoundParameters) 81 | if ($fakeBoundParameters.PoolName) { 82 | $Session = Get-CompleteSession $fakeBoundParameters.F5Session 83 | if ($Session) { 84 | Get-PoolMember -F5Session $Session -PoolName $fakeBoundParameters.PoolName -Partition $fakeBoundParameters.Partition -Name (Invoke-NullCoalescing {$fakeBoundParameters.Name} {'*'}) | 85 | Where-Object { $_.address -like "$wordToComplete*" } | 86 | Select-Object -ExpandProperty address 87 | } 88 | } 89 | } 90 | function CompletePoolMemberName { 91 | param($wordToComplete, 92 | $fakeBoundParameters) 93 | if ($fakeBoundParameters.PoolName) { 94 | $Session = Get-CompleteSession $fakeBoundParameters.F5Session 95 | if ($Session) { 96 | Get-PoolMember -F5Session $Session -PoolName $fakeBoundParameters.PoolName -Partition $fakeBoundParameters.Partition -Address (Invoke-NullCoalescing {$fakeBoundParameters.Address} {'*'}) | 97 | Where-Object { $_.name -like "$wordToComplete*" } | 98 | Select-Object -ExpandProperty name 99 | } 100 | } 101 | } 102 | function CompletePoolMonitorName { 103 | param($wordToComplete, 104 | $fakeBoundParameters) 105 | if ($fakeBoundParameters.PoolName) { 106 | $Session = Get-CompleteSession $fakeBoundParameters.F5Session 107 | if ($Session) { 108 | Get-PoolMonitor -F5Session $Session -PoolName $fakeBoundParameters.PoolName -Partition $fakeBoundParameters.Partition | 109 | Where-Object { $_.name -match "\b$wordToComplete*" } | 110 | Select-Object -ExpandProperty name 111 | } 112 | } 113 | } 114 | function CompletePoolName { 115 | param($wordToComplete, 116 | $fakeBoundParameters) 117 | $Session = Get-CompleteSession $fakeBoundParameters.F5Session 118 | if ($Session) { 119 | Get-Pool -F5Session $Session -Partition $fakeBoundParameters.Partition | Where-Object { $_.name -like "$wordToComplete*" -or $_.fullPath -like "$wordToComplete*" } | ForEach-Object { 120 | if ($fakeBoundParameters.Partition) { 121 | $_.name 122 | } else { 123 | $_.fullPath 124 | } 125 | } 126 | } 127 | } 128 | function CompleteRuleName { 129 | param($wordToComplete, 130 | $fakeBoundParameters) 131 | $Session = Get-CompleteSession $fakeBoundParameters.F5Session 132 | if ($Session) { 133 | Get-iRule -F5Session $Session -Partition $fakeBoundParameters.Partition | Where-Object { $_.name -like "$wordToComplete*" -or $_.fullPath -like "$wordToComplete" } | ForEach-Object { 134 | if ($fakeBoundParameters.Partition) { 135 | $_.name 136 | } else { 137 | $_.fullPath 138 | } 139 | } 140 | } 141 | } 142 | function CompleteVirtualServerName { 143 | param($wordToComplete, 144 | $fakeBoundParameters) 145 | $Session = Get-CompleteSession $fakeBoundParameters.F5Session 146 | if ($Session) { 147 | Get-VirtualServer -F5Session $Session -Partition $fakeBoundParameters.Partition | Where-Object { $_.name -like "$wordToComplete*" -or $_.fullPath -like "$wordToComplete" } | ForEach-Object { 148 | if ($fakeBoundParameters.Partition) { 149 | $_.name 150 | } else { 151 | $_.fullPath 152 | } 153 | } 154 | } 155 | } 156 | if (Get-Command Register-ArgumentCompleter -ErrorAction Ignore) 157 | { 158 | Register-ArgumentCompleter ` 159 | -CommandName @(Get-F5Command '*-PoolMember') ` 160 | -ParameterName Address ` 161 | -ScriptBlock $function:CompletePoolMemberAddress 162 | 163 | Register-ArgumentCompleter ` 164 | -CommandName @(Get-F5Command '*-PoolMember') ` 165 | -ParameterName Name ` 166 | -ScriptBlock $function:CompletePoolMemberName 167 | 168 | Register-ArgumentCompleter ` 169 | -CommandName @(Get-F5Command '*-PoolMonitor') ` 170 | -ParameterName Name ` 171 | -ScriptBlock $function:CompletePoolMonitorName 172 | 173 | Register-ArgumentCompleter ` 174 | -CommandName @(Get-F5Command '*-HealthMonitor') ` 175 | -ParameterName Name ` 176 | -ScriptBlock $function:CompleteMonitorName 177 | 178 | Register-ArgumentCompleter ` 179 | -CommandName @(Get-F5Command '*-HealthMonitor') ` 180 | -ParameterName Type ` 181 | -ScriptBlock $function:CompleteMonitorType 182 | 183 | Register-ArgumentCompleter ` 184 | -CommandName @(Get-F5Command '*-Node') ` 185 | -ParameterName Address ` 186 | -ScriptBlock $function:CompleteNodeAddress 187 | 188 | Register-ArgumentCompleter ` 189 | -CommandName @(Get-F5Command '*-Node') ` 190 | -ParameterName Name ` 191 | -ScriptBlock $function:CompleteNodeName 192 | 193 | Register-ArgumentCompleter ` 194 | -CommandName @(Get-F5Command '*-Pool') ` 195 | -ParameterName Name ` 196 | -ScriptBlock $function:CompletePoolName 197 | 198 | Register-ArgumentCompleter ` 199 | -CommandName @(Get-F5Command '*') ` 200 | -ParameterName Partition ` 201 | -ScriptBlock $function:CompletePartition 202 | 203 | Register-ArgumentCompleter ` 204 | -CommandName 'Get-BIGIPPartition' ` 205 | -ParameterName Name ` 206 | -ScriptBlock $function:CompleteBIGIPPartition 207 | 208 | Register-ArgumentCompleter ` 209 | -CommandName @(Get-F5Command '*-Pool*') ` 210 | -ParameterName PoolName ` 211 | -ScriptBlock $function:CompletePoolName 212 | 213 | Register-ArgumentCompleter ` 214 | -CommandName 'Get-iRule' ` 215 | -ParameterName Name ` 216 | -ScriptBlock $function:CompleteRuleName 217 | 218 | Register-ArgumentCompleter ` 219 | -CommandName @(Get-F5Command '*-VirtualServer') ` 220 | -ParameterName Name ` 221 | -ScriptBlock $function:CompleteVirtualServerName 222 | } 223 | Else { 224 | 225 | Write-Verbose "The Register-ArgumentCompleter cmdlet requires either PowerShell v5+ or the installation of the TabExpansionPlusPlus module (https://github.com/lzybkr/TabExpansionPlusPlus)" 226 | 227 | } -------------------------------------------------------------------------------- /F5-LTM/Private/Join-Object.ps1: -------------------------------------------------------------------------------- 1 | Function Join-Object { 2 | [CmdletBinding()] 3 | [OutputType([int])] 4 | Param ( 5 | [Parameter(Mandatory=$true)] 6 | [AllowNull()] 7 | [object[]] $Left, 8 | 9 | [Parameter(Mandatory=$true)] 10 | [AllowNull()] 11 | [object[]] $Right, 12 | 13 | # Property or Expression to use to compare values on the left 14 | [Alias('OnLeft')] 15 | [ValidateScript({ 16 | If ($_ -or $Join -eq 'FULL') { 17 | $True 18 | } else { 19 | Throw '-On is required for INNER,LEFT, and RIGHT joins' 20 | } 21 | })] 22 | [String[]] $On={1}, 23 | 24 | # Property or Expression to use to compare values on the right 25 | [Parameter(Mandatory=$false)] 26 | [String[]] $OnRight=$On, 27 | 28 | [object[]] $LeftProperty, 29 | [object[]] $RightProperty, 30 | 31 | [Parameter(Mandatory=$false)] 32 | [ValidateSet('INNER','LEFT','FULL','RIGHT')] 33 | [string] $Join='INNER' 34 | ) 35 | begin { 36 | if ($null -eq $LeftProperty) { 37 | $LeftProperty = $Left | Select-Object -First 1 | Get-Member -MemberType Properties -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Name 38 | } 39 | if ($null -eq $RightProperty) { 40 | $RightProperty = $Right | Select-Object -First 1 | Get-Member -MemberType Properties -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Name 41 | } 42 | $AllProperty = $LeftProperty + $RightProperty | Select-Object -Unique 43 | 44 | $LeftGroups = $Left | Group-Object $($On) -AsHashTable -AsString 45 | $RightGroups = $Right | Group-Object $($OnRight) -AsHashTable -AsString 46 | } 47 | process { 48 | if ($null -eq $Left -and $null -eq $Right) { 49 | $null 50 | } elseif (($null -eq $Left -or $null -eq $Right) -and 'INNER' -eq $Join) { 51 | $null 52 | } elseif ($null -eq $Left -and 'RIGHT','FULL' -contains $Join) { 53 | Write-Warning 'Left object is null, returning Right only' 54 | $Right | Select-Object -Property $AllProperty -ErrorAction SilentlyContinue 55 | } elseif ($null -eq $Right -and 'LEFT','FULL' -contains $Join) { 56 | Write-Warning 'Right object is null, returning Left only' 57 | $Left | Select-Object -Property $AllProperty -ErrorAction SilentlyContinue 58 | } else { 59 | # Output left items 60 | foreach($key in $LeftGroups.Keys) { 61 | foreach($leftItem in $leftGroups[$key]) { 62 | if ($RightGroups.ContainsKey($key)) { 63 | foreach($rightItem in $RightGroups[$key]) { 64 | # Matches are output for ALL Joins 65 | $output = $leftItem | Select-Object -Property $AllProperty 66 | foreach($p in $RightProperty) { 67 | $output.$p = $rightItem.$p 68 | } 69 | $output 70 | } 71 | } else { 72 | # Left items are output without matches for LEFT and FULL joins 73 | if ('LEFT' -eq $Join) { 74 | $leftItem | Select-Object -Property $AllProperty 75 | } 76 | } 77 | } 78 | } 79 | # Right items are output without matches for RIGHT and FULL joins 80 | if ('RIGHT','FULL' -contains $Join) { 81 | foreach($key in $RightGroups.Keys) { 82 | if (-not $LeftGroups.ContainsKey($key)) { 83 | foreach($rightItem in $rightGroups[$key]) { 84 | $rightItem | Select-Object -Property $AllProperty 85 | } 86 | } 87 | } 88 | } 89 | } 90 | 91 | } 92 | <# 93 | .Synopsis 94 | Join data from two sets of objects based on a common value 95 | .DESCRIPTION 96 | Join data from two sets of objects based on a common value 97 | 98 | For more details, see the accompanying blog post: 99 | http://ramblingcookiemonster.github.io/Join-Object/ 100 | https://github.com/RamblingCookieMonster/PowerShell/blob/master/Join-Object.ps1 101 | 102 | For even more details, see the original code and discussions that this borrows from: 103 | Dave Wyatt's Join-Object - http://powershell.org/wp/forums/topic/merging-very-large-collections 104 | Lucio Silveira's Join-Object - http://blogs.msdn.com/b/powershell/archive/2012/07/13/join-object.aspx 105 | 106 | .PARAMETER Left 107 | 'Left' collection of objects to join. You can use the pipeline for Left. 108 | 109 | The objects in this collection should be consistent. 110 | We look at the properties on the first object for a baseline. 111 | 112 | .PARAMETER Right 113 | 'Right' collection of objects to join. 114 | 115 | The objects in this collection should be consistent. 116 | We look at the properties on the first object for a baseline. 117 | 118 | .PARAMETER LeftJoinProperty 119 | Property on Left collection objects that we match up with RightJoinProperty on the Right collection 120 | 121 | .PARAMETER RightJoinProperty 122 | Property on Right collection objects that we match up with LeftJoinProperty on the Left collection 123 | 124 | .PARAMETER LeftProperties 125 | One or more properties to keep from Left. Default is to keep all Left properties (*). 126 | 127 | Each property can: 128 | - Be a plain property name like 'Name' 129 | - Contain wildcards like '*' 130 | - Be a hashtable like @{Name='Product Name';Expression={$_.Name}}. 131 | Name is the output property name 132 | Expression is the property value ($_ as the current object) 133 | 134 | Alternatively, use the Suffix or Prefix parameter to avoid collisions 135 | Each property using this hashtable syntax will be excluded from suffixes and prefixes 136 | 137 | .PARAMETER RightProperties 138 | One or more properties to keep from Right. Default is to keep all Right properties (*). 139 | 140 | Each property can: 141 | - Be a plain property name like 'Name' 142 | - Contain wildcards like '*' 143 | - Be a hashtable like @{Name='Product Name';Expression={$_.Name}}. 144 | Name is the output property name 145 | Expression is the property value ($_ as the current object) 146 | 147 | Alternatively, use the Suffix or Prefix parameter to avoid collisions 148 | Each property using this hashtable syntax will be excluded from suffixes and prefixes 149 | 150 | .PARAMETER Prefix 151 | If specified, prepend Right object property names with this prefix to avoid collisions 152 | 153 | Example: 154 | Property Name = 'Name' 155 | Suffix = 'j_' 156 | Resulting Joined Property Name = 'j_Name' 157 | 158 | .PARAMETER Suffix 159 | If specified, append Right object property names with this suffix to avoid collisions 160 | 161 | Example: 162 | Property Name = 'Name' 163 | Suffix = '_j' 164 | Resulting Joined Property Name = 'Name_j' 165 | 166 | .PARAMETER Type 167 | Type of join. Default is AllInLeft. 168 | 169 | AllInLeft will have all elements from Left at least once in the output, and might appear more than once 170 | if the where clause is true for more than one element in right, Left elements with matches in Right are 171 | preceded by elements with no matches. 172 | SQL equivalent: outer left join (or simply left join) 173 | 174 | AllInRight is similar to AllInLeft. 175 | 176 | OnlyIfInBoth will cause all elements from Left to be placed in the output, only if there is at least one 177 | match in Right. 178 | SQL equivalent: inner join (or simply join) 179 | 180 | AllInBoth will have all entries in right and left in the output. Specifically, it will have all entries 181 | in right with at least one match in left, followed by all entries in Right with no matches in left, 182 | followed by all entries in Left with no matches in Right. 183 | SQL equivalent: full join 184 | 185 | .EXAMPLE 186 | # 187 | #Define some input data. 188 | 189 | $l = 1..5 | Foreach-Object { 190 | [pscustomobject]@{ 191 | Name = "jsmith$_" 192 | Birthday = (Get-Date).adddays(-1) 193 | } 194 | } 195 | 196 | $r = 4..7 | Foreach-Object{ 197 | [pscustomobject]@{ 198 | Department = "Department $_" 199 | Name = "Department $_" 200 | Manager = "jsmith$_" 201 | } 202 | } 203 | 204 | #We have a name and Birthday for each manager, how do we find their department, using an inner join? 205 | Join-Object -Left $l -Right $r -LeftJoinProperty Name -RightJoinProperty Manager -Type OnlyIfInBoth -RightProperties Department 206 | 207 | 208 | # Name Birthday Department 209 | # ---- -------- ---------- 210 | # jsmith4 4/14/2015 3:27:22 PM Department 4 211 | # jsmith5 4/14/2015 3:27:22 PM Department 5 212 | 213 | .EXAMPLE 214 | # 215 | #Define some input data. 216 | 217 | $l = 1..5 | Foreach-Object { 218 | [pscustomobject]@{ 219 | Name = "jsmith$_" 220 | Birthday = (Get-Date).adddays(-1) 221 | } 222 | } 223 | 224 | $r = 4..7 | Foreach-Object{ 225 | [pscustomobject]@{ 226 | Department = "Department $_" 227 | Name = "Department $_" 228 | Manager = "jsmith$_" 229 | } 230 | } 231 | 232 | #We have a name and Birthday for each manager, how do we find all related department data, even if there are conflicting properties? 233 | $l | Join-Object -Right $r -LeftJoinProperty Name -RightJoinProperty Manager -Type AllInLeft -Prefix j_ 234 | 235 | # Name Birthday j_Department j_Name j_Manager 236 | # ---- -------- ------------ ------ --------- 237 | # jsmith1 4/14/2015 3:27:22 PM 238 | # jsmith2 4/14/2015 3:27:22 PM 239 | # jsmith3 4/14/2015 3:27:22 PM 240 | # jsmith4 4/14/2015 3:27:22 PM Department 4 Department 4 jsmith4 241 | # jsmith5 4/14/2015 3:27:22 PM Department 5 Department 5 jsmith5 242 | 243 | .EXAMPLE 244 | # 245 | #Hey! You know how to script right? Can you merge these two CSVs, where Path1's IP is equal to Path2's IP_ADDRESS? 246 | 247 | #Get CSV data 248 | $s1 = Import-CSV $Path1 249 | $s2 = Import-CSV $Path2 250 | 251 | #Merge the data, using a full outer join to avoid omitting anything, and export it 252 | Join-Object -Left $s1 -Right $s2 -LeftJoinProperty IP_ADDRESS -RightJoinProperty IP -Prefix 'j_' -Type AllInBoth | 253 | Export-CSV $MergePath -NoTypeInformation 254 | 255 | .EXAMPLE 256 | # 257 | # "Hey Warren, we need to match up SSNs to Active Directory users, and check if they are enabled or not. 258 | # I'll e-mail you an unencrypted CSV with all the SSNs from gmail, what could go wrong?" 259 | 260 | # Import some SSNs. 261 | $SSNs = Import-CSV -Path D:\SSNs.csv 262 | 263 | #Get AD users, and match up by a common value, samaccountname in this case: 264 | Get-ADUser -Filter "samaccountname -like 'wframe*'" | 265 | Join-Object -LeftJoinProperty samaccountname -Right $SSNs ` 266 | -RightJoinProperty samaccountname -RightProperties ssn ` 267 | -LeftProperties samaccountname, enabled, objectclass 268 | 269 | .NOTES 270 | This borrows from: 271 | Dave Wyatt's Join-Object - http://powershell.org/wp/forums/topic/merging-very-large-collections/ 272 | Lucio Silveira's Join-Object - http://blogs.msdn.com/b/powershell/archive/2012/07/13/join-object.aspx 273 | 274 | Changes: 275 | Always display full set of properties 276 | Display properties in order (left first, right second) 277 | If specified, add suffix or prefix to right object property names to avoid collisions 278 | Use a hashtable rather than ordereddictionary (avoid case sensitivity) 279 | 280 | .LINK 281 | http://ramblingcookiemonster.github.io/Join-Object/ 282 | 283 | .FUNCTIONALITY 284 | PowerShell Language 285 | #> 286 | } --------------------------------------------------------------------------------