PSWinBGP.psm1

[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
    'PSAvoidAssignmentToAutomaticVariable', 'IsWindows',
    Justification = 'IsWindows doesnt exist in PS5.1'
)]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
    'PSUseDeclaredVarsMoreThanAssignments', 'IsWindows',
    Justification = 'IsWindows doesnt exist in PS5.1'
)]
[CmdletBinding()]
param()
$baseName = [System.IO.Path]::GetFileNameWithoutExtension($PSCommandPath)
$script:PSModuleInfo = Import-PowerShellDataFile -Path "$PSScriptRoot\$baseName.psd1"
$script:PSModuleInfo | Format-List | Out-String -Stream | ForEach-Object { Write-Debug $_ }
$scriptName = $script:PSModuleInfo.Name
Write-Debug "[$scriptName] - Importing module"

if ($PSEdition -eq 'Desktop') {
    $IsWindows = $true
}

#region [init]
Write-Debug "[$scriptName] - [init] - Processing folder"
#region [init] - [Initialize-PSWinBGP]
Write-Debug "[$scriptName] - [init] - [Initialize-PSWinBGP] - Importing"
# Initializing PSWinBGP (Command completer is used by some public functions)
Register-ArgumentCompleter `
    -CommandName Get-WinBGPRoute, Start-WinBGPRoute, Stop-WinBGPRoute, Start-WinBGPRouteMaintenance, Stop-WinBGPRouteMaintenance `
    -ParameterName Name -ScriptBlock {
    param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
    # Define paramaters to $null to avoid syntax errors
    $null = $commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters

    # Dynamically generate routes array
    if ($FakeBoundParameters.ComputerName) {
        [Array] $routes = (Get-WinBGPRoute -ComputerName $FakeBoundParameters.ComputerName)
    } else {
        [Array] $routes = (Get-WinBGPRoute)
    }
    # Return routes as arguments (IntelliSense)
    $routes | ForEach-Object {
        New-Object -Type System.Management.Automation.CompletionResult -ArgumentList `
            $_.Name, `
            "$(if ($_.ComputerName){"ComputerName: '$($_.ComputerName)' - RouteName: '$($_.Name)'"}else{$_.Name})", `
            "ParameterValue", `
            "$(if ($_.ComputerName){"ComputerName: '$($_.ComputerName)' - "})Network: '$($_.Network)' - Status: '$($_.Status)'"
    }
}
Write-Debug "[$scriptName] - [init] - [Initialize-PSWinBGP] - Done"
#endregion [init] - [Initialize-PSWinBGP]
Write-Debug "[$scriptName] - [init] - Done"
#endregion [init]
#region [functions] - [private]
Write-Debug "[$scriptName] - [functions] - [private] - Processing folder"
#region [functions] - [private] - [Invoke-PSWinBGP]
Write-Debug "[$scriptName] - [functions] - [private] - [Invoke-PSWinBGP] - Importing"
function Invoke-PSWinBGP() {
    <#
        .SYNOPSIS
            Inkoke WinBGP
        .DESCRIPTION
            Inkoke WinBGP using CLI for local call or API
        .PARAMETER ComputerName
            Single or multiple ComputerName (Default: localhost)
        .EXAMPLE
            Invoke-PSWinBGP -ComputerName machine1,machine2 -Call routes
            # Get WinBGP status (Display WinBGP routes status)
    #>


    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [String[]]$ComputerName,
        [Parameter(ParameterSetName = 'call', Mandatory = $true)]
        [String]$Call, # Call to perform
        [Parameter(Mandatory = $false)]
        [String]$RouteName     # Select route to control
    )

    if ($ComputerName -eq 'local') {
        switch ($call) {
            'routes' { Invoke-Command { WinBGP } }
            'startroute' { Invoke-Command { WinBGP -RouteName $RouteName -StartRoute } }
            'stoproute' { Invoke-Command { WinBGP -RouteName $RouteName -StopRoute } }
            'startmaintenance' { Invoke-Command { WinBGP -RouteName $RouteName -StartMaintenance } }
            'stopmaintenance' { Invoke-Command { WinBGP -RouteName $RouteName -StopMaintenance } }
        }
    } else {
        if ($ComputerName -eq 'localhost') {
            [String]$AuthenticationMethod = $Script:PSWinBGP.LocalhostApiAuthenticationMethod
            [Int]$Port = $Script:PSWinBGP.LocalhostApiPort
            [String]$Protocol = $Script:PSWinBGP.LocalhostApiProtocol
            [Int]$Timeout = $Script:PSWinBGP.LocalhostApiTimeout
        } else {
            [String]$AuthenticationMethod = $Script:PSWinBGP.ApiAuthenticationMethod
            [Int]$Port = $Script:PSWinBGP.ApiPort
            [String]$Protocol = $Script:PSWinBGP.ApiProtocol
            [Int]$Timeout = $Script:PSWinBGP.ApiTimeout
        }

        # Initialize output variable
        $Output = @()
        $ErrorOutput = @()
        # Initialize error variable
        $ErrorCount = 0

        foreach ($Computer in $ComputerName) {
            # Initialize output variable
            $ApiOutput = [PSCustomObject]@{}
            $ErrorOut = [PSCustomObject]@{}
            $ApiDefaultRequestURL = "$($Protocol)://$($Computer):$($Port)/api"
            $params = @{}
            $params.add('UseBasicParsing', $true)
            $params.add('TimeoutSec', $Timeout)
            $params.add('ContentType', 'application/json')
            # Only authentication method managed currently is 'Negotiate' (To be improved)
            if (($AuthenticationMethod -eq 'IntegratedWindowsAuthentication') -or ($AuthenticationMethod -eq 'Negotiate')) {
                $params.add('UseDefaultCredentials', $true)
            }

            # Get
            if ($call -eq 'routes') {
                $ApiRequestURL = "$ApiDefaultRequestURL/routes"
            }
            # Post
            if (($call -eq 'startmaintenance') -or ($call -eq 'stopmaintenance') -or ($call -eq 'startroute') -or ($call -eq 'stoproute')) {
                $ApiRequestMethod = 'Post'
                if ($call -eq 'startmaintenance') {
                    $ApiRequestURL = "$ApiDefaultRequestURL/startmaintenance?routename=$RouteName"
                }
                if ($call -eq 'stopmaintenance') {
                    $ApiRequestURL = "$ApiDefaultRequestURL/stopmaintenance?routename=$RouteName"
                }
                if ($call -eq 'startroute') {
                    $ApiRequestURL = "$ApiDefaultRequestURL/startroute?routename=$RouteName"
                }
                if ($call -eq 'stoproute') {
                    $ApiRequestURL = "$ApiDefaultRequestURL/stoproute?routename=$RouteName"
                }
            }
            # Test if target is reachable
            if ($PSVersionTable.PSVersion.Major -ge 7) {
                $ConnectivityTest = (Test-Connection -TcpPort $Port -TimeoutSeconds $Timeout -TargetName $Computer -Quiet)
                $params.add('SkipHttpErrorCheck', $true)
                $params.add('StatusCodeVariable', 'StatusCode')
            } else {
                if ($ComputerName -eq 'localhost') {
                    # Bypass connectivity test when localhost (For speed performance)
                    $ConnectivityTest = $true
                } else {
                    $ConnectivityTest = (Test-NetConnection -ComputerName $computer -Port $port).TcpTestSucceeded
                }
            }
            if ($ConnectivityTest) {
                $params.add('uri', $ApiRequestURL)
                if ($ApiRequestMethod) {
                    $params.add('Method', $ApiRequestMethod)
                }
                # Perform Rest API Call
                if ($PSVersionTable.PSVersion.Major -ge 7) {
                    $RestApiCall = Invoke-RestMethod @params
                } else {
                    # Try/catch because PS5 don't support status code and skip http error check
                    try {
                        $RestApiCall = Invoke-RestMethod @params
                    } catch {
                        $ErrorOut | Add-member -MemberType NoteProperty -Name 'Result' -Value "API call error: $($_)"
                        $ErrorCount++
                    }
                }

                if ($RestApiCall) {
                    $ApiOutput = $RestApiCall
                    $ErrorOut | Add-member -MemberType NoteProperty -Name 'Result' -Value 'API connection OK'
                } else {
                    if ($StatusCode) {
                        $Value = "API return code: $([System.Net.HttpStatusCode]$StatusCode) ($StatusCode)"
                        $ErrorOut | Add-Member -MemberType NoteProperty -Name 'Result' -Value $Value
                        $ErrorCount++
                    } else {
                        if ($PSVersionTable.PSVersion.Major -ge 7) {
                            $ErrorOut | Add-member -MemberType NoteProperty -Name 'Result' -Value 'API call timeout'
                            $ErrorCount++
                        }
                    }
                }
            } else {
                $ErrorOut | Add-member -MemberType NoteProperty -Name 'Result' -Value 'API connection timeout'
                $ErrorCount++
            }
            # Add ComputerName variable to output (except for localhost)
            if ($Computer -ne 'localhost') {
                $ApiOutput | Add-member -MemberType NoteProperty -Name 'ComputerName' -Value "$Computer"
            }
            $Output += $ApiOutput
            # Add ComputerName variable to output (except for localhost)
            if ($Computer -ne 'localhost') {
                $ErrorOut | Add-member -MemberType NoteProperty -Name 'ComputerName' -Value "$Computer"
            }
            $ErrorOutput += $ErrorOut
        }

        # If there is connection error, just return connection table
        if ($ErrorCount -eq 0) {
            # Return result
            return [PSCustomObject]$Output
        } else {
            return [PSCustomObject]$ErrorOutput
        }
    }
}
Write-Debug "[$scriptName] - [functions] - [private] - [Invoke-PSWinBGP] - Done"
#endregion [functions] - [private] - [Invoke-PSWinBGP]
Write-Debug "[$scriptName] - [functions] - [private] - Done"
#endregion [functions] - [private]
#region [functions] - [public]
Write-Debug "[$scriptName] - [functions] - [public] - Processing folder"
#region [functions] - [public] - [Get-WinBGPRoute]
Write-Debug "[$scriptName] - [functions] - [public] - [Get-WinBGPRoute] - Importing"
function Get-WinBGPRoute() {
    <#
        .SYNOPSIS
            WinBGP Remote Management - Get WinBGP Route
        .DESCRIPTION
            This function retrieve WinBGP Routes
        .PARAMETER ComputerName
            Single or multiple ComputerName (Default: localhost)
        .PARAMETER Name
            Single or multiple Route Name (IntelliSense availalble)
        .EXAMPLE
            Get-WinBGPRoute -ComputerName machine1,machine2
    #>


    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)]
        [String[]]$ComputerName = 'local',
        [Parameter(Mandatory = $false)]
        [Alias('RouteName')]
        [String[]]$Name
    )
    process {
        $Routes = Invoke-PSWinBGP -ComputerName $ComputerName -Call 'routes'
        # Filter if Name is provided
        if ($Name) {
            $Routes | Where-Object { $_.Name -in $Name }
        } else {
            $Routes
        }
    }
}
Write-Debug "[$scriptName] - [functions] - [public] - [Get-WinBGPRoute] - Done"
#endregion [functions] - [public] - [Get-WinBGPRoute]
#region [functions] - [public] - [Set-PSWinBGPConfig]
Write-Debug "[$scriptName] - [functions] - [public] - [Set-PSWinBGPConfig] - Importing"
function Set-PSWinBGPConfig {
    <#
    .SYNOPSIS
        Set PSWinBBGP module configuration.

    .DESCRIPTION
        Set PSWinBBGP module configuration, and $PSWinBBGP module variable.

    .PARAMETER ApiAuthenticationMethod
        API Authentication Method (Default: IntegratedWindowsAuthentication)

    .PARAMETER ApiPort
        API Port (Default: 8888)

    .PARAMETER ApiProtocol
        API Protocol (Default: HTTPS)

    .PARAMETER ApiTimeout
        API Timeout (Default: 10s)

    .PARAMETER LocalhostApiAuthenticationMethod
        Localhost API Authentication Method (Default: Anonymous)

    .PARAMETER LocalhostApiPort
        Localhost API Port (Default: 8888)

    .PARAMETER LocalhostApiProtocol
        Localhost API Protocol (Default: HTTP)

    .PARAMETER LocalhostApiTimeout
        Localhost API Timeout (Default: 5s)
    #>

    [CmdletBinding(SupportsShouldProcess = $true)]
    param(
        [string]$ApiAuthenticationMethod,
        [Int]$ApiPort,
        [string]$ApiProtocol,
        [Int]$ApiTimeout,
        [string]$LocalhostApiAuthenticationMethod,
        [Int]$LocalhostApiPort,
        [string]$LocalhostApiProtocol,
        [Int]$LocalhostApiTimeout
    )

    if ($pscmdlet.ShouldProcess('$Script:PSWinBGP', 'Set config')) {
        switch ($PSBoundParameters.Keys) {
            'ApiAuthenticationMethod' { $Script:PSWinBGP.ApiAuthenticationMethod = $ApiAuthenticationMethod }
            'ApiPort' { $Script:PSWinBGP.ApiPort = $ApiPort }
            'ApiProtocol' { $Script:PSWinBGP.ApiProtocol = $ApiProtocol }
            'ApiTimeout' { $Script:PSWinBGP.ApiTimeout = $ApiTimeout }
            'LocalhostApiAuthenticationMethod' { $Script:PSWinBGP.LocalhostApiAuthenticationMethod = $LocalhostApiAuthenticationMethod }
            'LocalhostApiPort' { $Script:PSWinBGP.LocalhostApiPort = $LocalhostApiPort }
            'LocalhostApiProtocol' { $Script:PSWinBGP.LocalhostApiProtocol = $LocalhostApiProtocol }
            'LocalhostApiTimeout' { $Script:PSWinBGP.LocalhostApiTimeout = $LocalhostApiTimeout }
        }
    }
}
Write-Debug "[$scriptName] - [functions] - [public] - [Set-PSWinBGPConfig] - Done"
#endregion [functions] - [public] - [Set-PSWinBGPConfig]
#region [functions] - [public] - [Start-WinBGPRoute]
Write-Debug "[$scriptName] - [functions] - [public] - [Start-WinBGPRoute] - Importing"
function Start-WinBGPRoute() {
    <#
        .SYNOPSIS
            WinBGP Remote Management - Start Route
        .DESCRIPTION
            This function perform Start Route
        .PARAMETER ComputerName
            Single or multiple ComputerName (Default: localhost)
        .PARAMETER Name
            Single or multiple Route Name (IntelliSense availalble)
        .EXAMPLE
            Start-WinBGPRoute -ComputerName machine1,machine2 -Name route1.contoso.com
    #>


    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')]
    param(
        [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)]
        [String[]]$ComputerName = 'local',
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [Alias('RouteName')]
        [String[]]$Name
    )
    process {
        # Parsing all routes provided
        foreach ($Route in $Name) {
            # If action is confirmed
            if ($PSCmdlet.ShouldProcess("$($Route)$(if ($ComputerName -ne 'local'){" [ComputerName: $($ComputerName)]"})", 'Start WinBGP route')) {
                Invoke-PSWinBGP -ComputerName $ComputerName -call 'startroute' -RouteName $Route
            }
        }
    }
}
Write-Debug "[$scriptName] - [functions] - [public] - [Start-WinBGPRoute] - Done"
#endregion [functions] - [public] - [Start-WinBGPRoute]
#region [functions] - [public] - [Start-WinBGPRouteMaintenance]
Write-Debug "[$scriptName] - [functions] - [public] - [Start-WinBGPRouteMaintenance] - Importing"
function Start-WinBGPRouteMaintenance() {
    <#
        .SYNOPSIS
            WinBGP Remote Management - Start Route Maintenance
        .DESCRIPTION
            This function perform Start Route Maintenance
        .PARAMETER ComputerName
            Single or multiple ComputerName (Default: localhost)
        .PARAMETER Name
            Single or multiple Route Name (IntelliSense availalble)
        .EXAMPLE
            Start-WinBGPRouteMaintenance -ComputerName machine1,machine2 -Name route1.contoso.com
    #>


    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]
    param(
        [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)]
        [String[]]$ComputerName = 'local',
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [Alias('RouteName')]
        [String[]]$Name
    )
    process {
        # Parsing all routes provided
        foreach ($Route in $Name) {
            # If action is confirmed
            if (
                $PSCmdlet.ShouldProcess(
                    "$($Route)$(
                        if ($ComputerName -ne 'local') {
                            " [ComputerName: $($ComputerName)]"
                        }
                    )"
,
                    'Start WinBGP route maintenance'
                )
            ) {
                Invoke-PSWinBGP -ComputerName $ComputerName -call 'startmaintenance' -RouteName $Route
            }
        }
    }
}
Write-Debug "[$scriptName] - [functions] - [public] - [Start-WinBGPRouteMaintenance] - Done"
#endregion [functions] - [public] - [Start-WinBGPRouteMaintenance]
#region [functions] - [public] - [Stop-WinBGPRoute]
Write-Debug "[$scriptName] - [functions] - [public] - [Stop-WinBGPRoute] - Importing"
function Stop-WinBGPRoute() {
    <#
        .SYNOPSIS
            WinBGP Remote Management - Stop Route
        .DESCRIPTION
            This function perform Stop Route
        .PARAMETER ComputerName
            Single or multiple ComputerName (Default: localhost)
        .PARAMETER Name
            Single or multiple Route Name (IntelliSense availalble)
        .EXAMPLE
            Stop-WinBGPRoute -ComputerName machine1,machine2 -Name route1.contoso.com
    #>


    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')]
    param(
        [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)]
        [String[]]$ComputerName = 'local',
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [Alias('RouteName')]
        [String[]]$Name
    )
    process {
        # Parsing all routes provided
        foreach ($Route in $Name) {
            # If action is confirmed
            if ($PSCmdlet.ShouldProcess("$($Route)$(if ($ComputerName -ne 'local'){" [ComputerName: $($ComputerName)]"})", 'Stop WinBGP route')) {
                Invoke-PSWinBGP -ComputerName $ComputerName -call 'stoproute' -RouteName $Route
            }
        }
    }
}
Write-Debug "[$scriptName] - [functions] - [public] - [Stop-WinBGPRoute] - Done"
#endregion [functions] - [public] - [Stop-WinBGPRoute]
#region [functions] - [public] - [Stop-WinBGPRouteMaintenance]
Write-Debug "[$scriptName] - [functions] - [public] - [Stop-WinBGPRouteMaintenance] - Importing"
function Stop-WinBGPRouteMaintenance() {
    <#
        .SYNOPSIS
            WinBGP Remote Management - Stop Route Maintenance
        .DESCRIPTION
            This function perform Stop Route Maintenance
        .PARAMETER ComputerName
            Single or multiple ComputerName (Default: localhost)
        .PARAMETER Name
            Single or multiple Route Name (IntelliSense availalble)
        .EXAMPLE
            Stop-WinBGPRouteMaintenance -ComputerName machine1,machine2 -Name route1.contoso.com
    #>


    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]
    param(
        [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)]
        [String[]]$ComputerName = 'local',
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [Alias('RouteName')]
        [String[]]$Name
    )
    process {
        # Parsing all routes provided
        foreach ($Route in $Name) {
            # If action is confirmed
            if (
                $PSCmdlet.ShouldProcess(
                    "$($Route)$(
                        if ($ComputerName -ne 'local') {
                            " [ComputerName: $($ComputerName)]"
                        }
                    )"
,
                    'Stop WinBGP route maintenance'
                )
            ) {
                Invoke-PSWinBGP -ComputerName $ComputerName -call 'stopmaintenance' -RouteName $Route
            }
        }
    }
}
Write-Debug "[$scriptName] - [functions] - [public] - [Stop-WinBGPRouteMaintenance] - Done"
#endregion [functions] - [public] - [Stop-WinBGPRouteMaintenance]
Write-Debug "[$scriptName] - [functions] - [public] - Done"
#endregion [functions] - [public]
#region [variables] - [private]
Write-Debug "[$scriptName] - [variables] - [private] - Processing folder"
#region [variables] - [private] - [PSWinBGP-default]
Write-Debug "[$scriptName] - [variables] - [private] - [PSWinBGP-default] - Importing"
$script:PSWinBGP = @{
    LocalhostApiAuthenticationMethod = 'Anonymous'
    LocalhostApiPort                 = 8888
    LocalhostApiProtocol             = 'http'
    LocalhostApiTimeout              = 5
    ApiAuthenticationMethod          = 'IntegratedWindowsAuthentication'
    ApiPort                          = 8888
    ApiProtocol                      = 'https'
    ApiTimeout                       = 10
}
Write-Debug "[$scriptName] - [variables] - [private] - [PSWinBGP-default] - Done"
#endregion [variables] - [private] - [PSWinBGP-default]
Write-Debug "[$scriptName] - [variables] - [private] - Done"
#endregion [variables] - [private]

#region Member exporter
$exports = @{
    Alias    = '*'
    Cmdlet   = ''
    Function = @(
        'Get-WinBGPRoute'
        'Set-PSWinBGPConfig'
        'Start-WinBGPRoute'
        'Start-WinBGPRouteMaintenance'
        'Stop-WinBGPRoute'
        'Stop-WinBGPRouteMaintenance'
    )
    Variable = ''
}
Export-ModuleMember @exports
#endregion Member exporter