AzVpnConnect.psm1

$script:DefaultProfile = "MSFT-AzVPN-Manual"
$script:AzureVpnAppId = 'shell:AppsFolder\Microsoft.AzureVpn_8wekyb3d8bbwe!App'

function Get-AzVpnStatus {
    <#
    .SYNOPSIS
        Gets the connection status of an Azure VPN profile.
    .DESCRIPTION
        Queries the Windows VPN subsystem for the current connection status
        of the specified Azure VPN profile.
    .PARAMETER ProfileName
        The Windows VPN profile name. Defaults to MSFT-AzVPN-Manual.
    .EXAMPLE
        Get-AzVpnStatus
    .EXAMPLE
        Get-AzVpnStatus -ProfileName "MyVpn"
    #>

    [CmdletBinding()]
    param(
        [Parameter(Position = 0)]
        [string]$ProfileName = $script:DefaultProfile
    )

    $vpn = Get-VpnConnection -Name $ProfileName -ErrorAction Stop
    [PSCustomObject]@{
        ProfileName = $vpn.Name
        Status      = $vpn.ConnectionStatus
        Server      = $vpn.ServerAddress
        SplitTunnel = $vpn.SplitTunneling
    }
}

function Connect-AzVpn {
    <#
    .SYNOPSIS
        Ensures the Azure VPN is connected, authenticating if needed.
    .DESCRIPTION
        Checks the VPN status and connects if disconnected. Uses rasdial for
        headless connection when credentials are cached. If credentials have
        expired, launches the Azure VPN Client app for interactive authentication
        and polls until the connection comes up or the timeout is reached.
    .PARAMETER ProfileName
        The Windows VPN profile name. Defaults to MSFT-AzVPN-Manual.
    .PARAMETER TimeoutSeconds
        How long to wait for interactive authentication before giving up.
        Defaults to 60 seconds.
    .PARAMETER Force
        Connect even if already connected (reconnect).
    .EXAMPLE
        Connect-AzVpn
    .EXAMPLE
        Connect-AzVpn -TimeoutSeconds 120
    .EXAMPLE
        Connect-AzVpn -Force
    #>

    [CmdletBinding()]
    param(
        [Parameter(Position = 0)]
        [string]$ProfileName = $script:DefaultProfile,

        [Parameter()]
        [int]$TimeoutSeconds = 60,

        [Parameter()]
        [switch]$Force
    )

    $status = (Get-VpnConnection -Name $ProfileName -ErrorAction Stop).ConnectionStatus

    if (($status -eq 'Connected') -and (-not $Force)) {
        Write-Verbose "$ProfileName is already connected."
        return [PSCustomObject]@{ ProfileName = $ProfileName; Connected = $true; Method = 'AlreadyConnected' }
    }

    if (($status -eq 'Connected') -and $Force) {
        Write-Verbose "Force reconnect -- disconnecting first."
        $null = rasdial $ProfileName /disconnect 2>&1
        Start-Sleep -Seconds 2
    }

    if ($status -eq 'Connecting') {
        Write-Verbose "$ProfileName is mid-connect, waiting..."
        if (Wait-AzVpnConnected -ProfileName $ProfileName -TimeoutSeconds $TimeoutSeconds) {
            return [PSCustomObject]@{ ProfileName = $ProfileName; Connected = $true; Method = 'WaitedForExisting' }
        }
        throw "${ProfileName}: timed out waiting for in-progress connection."
    }

    # Try rasdial -- headless, works when credentials are cached
    Write-Verbose "Attempting headless connect via rasdial..."
    $null = rasdial $ProfileName 2>&1
    if ($LASTEXITCODE -eq 0) {
        Write-Verbose "rasdial succeeded."
        return [PSCustomObject]@{ ProfileName = $ProfileName; Connected = $true; Method = 'Rasdial' }
    }

    # Credentials expired -- open the Azure VPN Client for interactive auth
    $rasExit = $LASTEXITCODE
    Write-Warning "rasdial failed (exit $rasExit). Opening Azure VPN Client for authentication..."
    explorer.exe $script:AzureVpnAppId

    Write-Host "Please connect in the Azure VPN Client. Waiting up to ${TimeoutSeconds}s..." -ForegroundColor Cyan
    if (Wait-AzVpnConnected -ProfileName $ProfileName -TimeoutSeconds $TimeoutSeconds) {
        return [PSCustomObject]@{ ProfileName = $ProfileName; Connected = $true; Method = 'Interactive' }
    }

    throw "${ProfileName}: failed to connect within ${TimeoutSeconds}s."
}

function Disconnect-AzVpn {
    <#
    .SYNOPSIS
        Disconnects the Azure VPN.
    .PARAMETER ProfileName
        The Windows VPN profile name. Defaults to MSFT-AzVPN-Manual.
    .EXAMPLE
        Disconnect-AzVpn
    #>

    [CmdletBinding()]
    param(
        [Parameter(Position = 0)]
        [string]$ProfileName = $script:DefaultProfile
    )

    $status = (Get-VpnConnection -Name $ProfileName -ErrorAction Stop).ConnectionStatus
    if ($status -eq 'Disconnected') {
        Write-Verbose "$ProfileName is already disconnected."
        return
    }

    $null = rasdial $ProfileName /disconnect 2>&1
    if ($LASTEXITCODE -ne 0) {
        throw "Failed to disconnect $ProfileName (exit $LASTEXITCODE)."
    }

    Write-Verbose "$ProfileName disconnected."
}

function Wait-AzVpnConnected {
    <#
    .SYNOPSIS
        Polls until the Azure VPN connects or the timeout expires.
    .PARAMETER ProfileName
        The Windows VPN profile name.
    .PARAMETER TimeoutSeconds
        Maximum seconds to wait.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [string]$ProfileName,

        [Parameter()]
        [int]$TimeoutSeconds = 60
    )

    $deadline = (Get-Date).AddSeconds($TimeoutSeconds)
    while ((Get-Date) -lt $deadline) {
        $s = (Get-VpnConnection -Name $ProfileName -ErrorAction SilentlyContinue).ConnectionStatus
        if ($s -eq 'Connected') { return $true }
        Start-Sleep -Seconds 2
    }
    return $false
}

Export-ModuleMember -Function Connect-AzVpn, Disconnect-AzVpn, Get-AzVpnStatus