PSWinBGP.psm1

[CmdletBinding()]
param()
$scriptName = 'PSWinBGP'
Write-Verbose "[$scriptName] - Importing module"

#region - Data import
Write-Verbose "[$scriptName] - [data] - Processing folder"
$dataFolder = (Join-Path $PSScriptRoot 'data')
Write-Verbose "[$scriptName] - [data] - [$dataFolder]"
Get-ChildItem -Path "$dataFolder" -Recurse -Force -Include '*.psd1' -ErrorAction SilentlyContinue | ForEach-Object {
    Write-Verbose "[$scriptName] - [data] - [$($_.BaseName)] - Importing"
    New-Variable -Name $_.BaseName -Value (Import-PowerShellDataFile -Path $_.FullName) -Force
    Write-Verbose "[$scriptName] - [data] - [$($_.BaseName)] - Done"
}

Write-Verbose "[$scriptName] - [data] - Done"
#endregion - Data import

#region - From [init]
Write-Verbose "[$scriptName] - [init] - Processing folder"

#region - From [init] - [Initialize-PSWinBGP]
Write-Verbose "[$scriptName] - [init] - [Initialize-PSWinBGP] - Importing"

# Initializing PSWinBGP (Command completer is used by some public functions)
Register-ArgumentCompleter `
    -CommandName Start-WinBGPRoute, Stop-WinBGPRoute, Start-WinBGPRouteMaintenance, Stop-WinBGPRouteMaintenance `
    -ParameterName RouteName -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-Verbose "[$scriptName] - [init] - [Initialize-PSWinBGP] - Done"
#endregion - From [init] - [Initialize-PSWinBGP]

Write-Verbose "[$scriptName] - [init] - Done"
#endregion - From [init]

#region - From [functions] - [private]
Write-Verbose "[$scriptName] - [functions] - [private] - Processing folder"

#region - From [functions] - [private] - [Invoke-PSWinBGP]
Write-Verbose "[$scriptName] - [functions] - [private] - [Invoke-PSWinBGP] - Importing"

function Invoke-PSWinBGP() {
    <#
        .SYNOPSIS
            Inkoke WinBGP
        .DESCRIPTION
            Inkoke WinBGP using local method 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' { Send-WinBGPRouteControl -RouteName $RouteName -Control 'start' }
            'stoproute' { Send-WinBGPRouteControl -RouteName $RouteName -Control 'stop' }
            'startmaintenance' { Send-WinBGPRouteControl -RouteName $RouteName -Control 'start' -Action 'maintenance' }
            'stopmaintenance' { Send-WinBGPRouteControl -RouteName $RouteName -Control 'stop' -Action 'maintenance' }
        }
    } 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-Verbose "[$scriptName] - [functions] - [private] - [Invoke-PSWinBGP] - Done"
#endregion - From [functions] - [private] - [Invoke-PSWinBGP]
#region - From [functions] - [private] - [Send-WinBGPPipeMessage]
Write-Verbose "[$scriptName] - [functions] - [private] - [Send-WinBGPPipeMessage] - Importing"

function Send-WinBGPPipeMessage() {
    <#
        .SYNOPSIS
            Send-WinBGPPipeMessage
        .DESCRIPTION
            Send-WinBGPPipeMessage
        .PARAMETER PipeName
            PipeName
        .PARAMETER Message
            Message
        .EXAMPLE
            Send-WinBGPPipeMessage -PipeName $pipeName -Message $Message
    #>


    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [String]$PipeName,
        [Parameter(Mandatory = $true)]
        [String]$Message
    )
    $PipeDir = [System.IO.Pipes.PipeDirection]::Out
    $PipeOpt = [System.IO.Pipes.PipeOptions]::Asynchronous

    $pipe = $null # Named pipe stream
    $sw = $null   # Stream Writer
    try {
        $pipe = new-object System.IO.Pipes.NamedPipeClientStream(".", $PipeName, $PipeDir, $PipeOpt)
        $sw = new-object System.IO.StreamWriter($pipe)
        $pipe.Connect(1000)
        if (!$pipe.IsConnected) {
            throw "Failed to connect client to pipe $pipeName"
        }
        $sw.AutoFlush = $true
        $sw.WriteLine($Message)
    } catch {
        Write-Log "Error sending pipe $pipeName message: $_" -Level Error
    } finally {
        if ($sw) {
            $sw.Dispose() # Release resources
            $sw = $null   # Force the PowerShell garbage collector to delete the .net object
        }
        if ($pipe) {
            $pipe.Dispose() # Release resources
            $pipe = $null   # Force the PowerShell garbage collector to delete the .net object
        }
    }
}

Write-Verbose "[$scriptName] - [functions] - [private] - [Send-WinBGPPipeMessage] - Done"
#endregion - From [functions] - [private] - [Send-WinBGPPipeMessage]
#region - From [functions] - [private] - [Send-WinBGPRouteControl]
Write-Verbose "[$scriptName] - [functions] - [private] - [Send-WinBGPRouteControl] - Importing"

function Send-WinBGPRouteControl() {
    <#
        .SYNOPSIS
            Send-WinBGPRouteControl
        .DESCRIPTION
            Send-WinBGPRouteControl
        .PARAMETER RouteName
            RouteName
        .PARAMETER Control
            Control
        .PARAMETER Action
            Action
        .EXAMPLE
            Send-WinBGPRouteControl -RouteName $RouteName -Control 'start' -Action 'maintenance'
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false)]
        [String]$Action = 'route',
        [Parameter(Mandatory = $true)]
        [String]$RouteName,
        [Parameter(Mandatory = $true)]
        [String]$Control
    )
    $PipeStatus = $null
    # Performing Action
    try {
        # Temporary
        $pipeName = 'Service_WinBGP'
        $Message = "$($Action) $($RouteName) $($Control)"
        Send-WinBGPPipeMessage -PipeName $pipeName -Message $Message
    } catch {
        $PipeStatus = ($_).ToString()
    }
    if ($PipeStatus -like "*Pipe hasn't been connected yet*") {
        Write-Output "WinBGP not ready"
    } else {
        # TO BE IMPROVED to get status
        Write-Output "Success"
    }
}

Write-Verbose "[$scriptName] - [functions] - [private] - [Send-WinBGPRouteControl] - Done"
#endregion - From [functions] - [private] - [Send-WinBGPRouteControl]

Write-Verbose "[$scriptName] - [functions] - [private] - Done"
#endregion - From [functions] - [private]

#region - From [functions] - [public]
Write-Verbose "[$scriptName] - [functions] - [public] - Processing folder"

#region - From [functions] - [public] - [Get-WinBGPRoute]
Write-Verbose "[$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)
        .EXAMPLE
            Get-WinBGPRoute -ComputerName machine1,machine2
    #>


    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $false)]
        [String[]]$ComputerName = 'local'
    )

    Invoke-PSWinBGP -ComputerName $ComputerName -Call 'routes'
}

Write-Verbose "[$scriptName] - [functions] - [public] - [Get-WinBGPRoute] - Done"
#endregion - From [functions] - [public] - [Get-WinBGPRoute]
#region - From [functions] - [public] - [Set-PSWinBGPConfig]
Write-Verbose "[$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-Verbose "[$scriptName] - [functions] - [public] - [Set-PSWinBGPConfig] - Done"
#endregion - From [functions] - [public] - [Set-PSWinBGPConfig]
#region - From [functions] - [public] - [Start-WinBGPRoute]
Write-Verbose "[$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 RouteName
            RouteName (Currently supporting only one route, IntelliSense availalble)
        .EXAMPLE
            Start-WinBGPRoute -ComputerName machine1,machine2 -RouteName route1.contoso.com
    #>


    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]
    param(
        [Parameter(Mandatory = $false)]
        [String[]]$ComputerName = 'local',
        [Parameter(Mandatory = $true)]
        [String]$RouteName
    )
    # If action is confirmed
    if ($pscmdlet.ShouldProcess($RouteName, 'Start route')) {
        Invoke-PSWinBGP -ComputerName $ComputerName -call 'startroute' -RouteName $RouteName
    }
}

Write-Verbose "[$scriptName] - [functions] - [public] - [Start-WinBGPRoute] - Done"
#endregion - From [functions] - [public] - [Start-WinBGPRoute]
#region - From [functions] - [public] - [Start-WinBGPRouteMaintenance]
Write-Verbose "[$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 RouteName
            RouteName (Currently supporting only one route, IntelliSense availalble)
        .EXAMPLE
            Start-WinBGPRouteMaintenance -ComputerName machine1,machine2 -RouteName route1.contoso.com
    #>


    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]
    param(
        [Parameter(Mandatory = $false)]
        [String[]]$ComputerName = 'local',
        [Parameter(Mandatory = $true)]
        [String]$RouteName
    )

    if ($pscmdlet.ShouldProcess($RouteName, 'Start route maintenance')) {
        Invoke-PSWinBGP -ComputerName $ComputerName -call 'startmaintenance' -routename $RouteName
    }
}

Write-Verbose "[$scriptName] - [functions] - [public] - [Start-WinBGPRouteMaintenance] - Done"
#endregion - From [functions] - [public] - [Start-WinBGPRouteMaintenance]
#region - From [functions] - [public] - [Stop-WinBGPRoute]
Write-Verbose "[$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 RouteName
            RouteName (Currently supporting only one route, IntelliSense availalble)
        .EXAMPLE
            Stop-WinBGPRoute -ComputerName machine1,machine2 -RouteName route1.contoso.com
    #>


    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]
    param(
        [Parameter(Mandatory = $false)]
        [String[]]$ComputerName = 'local',
        [Parameter(Mandatory = $true)]
        [String]$RouteName
    )
    if ($pscmdlet.ShouldProcess($RouteName, 'Stop route')) {
        Invoke-PSWinBGP -ComputerName $ComputerName -call 'stoproute' -routename $RouteName
    }
}

Write-Verbose "[$scriptName] - [functions] - [public] - [Stop-WinBGPRoute] - Done"
#endregion - From [functions] - [public] - [Stop-WinBGPRoute]
#region - From [functions] - [public] - [Stop-WinBGPRouteMaintenance]
Write-Verbose "[$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 RouteName
            RouteName (Currently supporting only one route, IntelliSense availalble)
        .EXAMPLE
            Stop-WinBGPRouteMaintenance -ComputerName machine1,machine2 -RouteName route1.contoso.com
    #>


    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]
    param(
        [Parameter(Mandatory = $false)]
        [String[]]$ComputerName = 'local',
        [Parameter(Mandatory = $true)]
        [String]$RouteName
    )
    if ($pscmdlet.ShouldProcess($RouteName, 'Stop route maintenance')) {
        Invoke-PSWinBGP -ComputerName $ComputerName -call 'stopmaintenance' -routename $RouteName
    }
}

Write-Verbose "[$scriptName] - [functions] - [public] - [Stop-WinBGPRouteMaintenance] - Done"
#endregion - From [functions] - [public] - [Stop-WinBGPRouteMaintenance]

Write-Verbose "[$scriptName] - [functions] - [public] - Done"
#endregion - From [functions] - [public]


$exports = @{
    Alias    = '*'
    Cmdlet   = ''
    Function = @(
        'Get-WinBGPRoute'
        'Set-PSWinBGPConfig'
        'Start-WinBGPRoute'
        'Start-WinBGPRouteMaintenance'
        'Stop-WinBGPRoute'
        'Stop-WinBGPRouteMaintenance'
    )
}
Export-ModuleMember @exports