最近想实现在外面访问家里的迷你电脑,因为电脑的 IPv6 地址是通过 SLACC 分配的,不能在路由器上获得迷你电脑的 IPv6 地址,所以需要在电脑上实现 DDNS 功能。就写了一个脚本,实现了已下功能:

  1. 从本地网卡获取 IPv6 的第一个首选地址,通过 cloudfalre api 获取 dns 记录的 ipv6 地址
  2. 对比获取的 IPv6 地址与 cloudflare 中的 dns 记录是否一致
  3. 如果比对失败则通过 cloudflare api 更新 dns 解析
  4. 发生错误则发送 telegram 通知

下面是脚本内容:

## ------------------------------------------------------------------------------
# 脚本名称: UpdateDNS.ps1
# 描述: 遍历所有网卡(排除WiFi),获取本机 IPv6 公网地址,通过 Cloudflare API 更新 DNS 记录,并发送 Telegram 通知
# 作者: [will https://opswill.com]
# 日期: [2024/12/31]
# 版本: 1.3
# ------------------------------------------------------------------------------
# 注意: 请确保已正确配置 API Token 和其他变量
# ------------------------------------------------------------------------------

# 设置 PowerShell 执行策略(如果需要)
# Set-ExecutionPolicy Bypass -Scope Process -Force  # 建议仅在测试时使用,生产环境应避免使用 Bypass

# 配置 Cloudflare API 和 Telegram Bot 信息
$apiToken = ""
$zoneId = ""
$recordId = ""
$domain = ""
$botToken = ""
$chatId = ""

# 日志文件路径
$logFilePath = "C:\data\DDNS.log"

# 排除的网卡接口名称(正则表达式)
$excludedInterfaces = "Wi-Fi|Wireless|WLAN|Loopback|Bluetooth"

# 函数:记录日志
function Log-Message {
    param(
        [string]$Message
    )
    $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
    $logEntry = "$timestamp - $Message"
    Add-Content -Path $logFilePath -Value $logEntry
}

# 函数:发送 Telegram 消息
function Send-TelegramMessage {
    param(
        [string]$Message
    )
    $telegramApiUrl = "https://api.telegram.org/bot$botToken/sendMessage"
    $params = @{
        chat_id = $chatId
        text    = $Message
    }
    try {
        Invoke-RestMethod -Uri $telegramApiUrl -Method Post -Body $params
    }
    catch {
        Log-Message "发送 Telegram 通知失败: $_"
    }
}

# 函数:获取公网 IPv6 地址
function Get-PublicIPv6Address {
    $ipv6AddressesWithInterface = Get-NetIPAddress -AddressFamily IPv6 | Where-Object {
        $_.InterfaceAlias -notmatch $excludedInterfaces -and
        $_.PrefixOrigin -in "RouterAdvertisement", "DHCPv6" -and
        $_.SuffixOrigin -ne "Random" -and
        $_.AddressState -eq "Preferred" # 确保地址是首选状态
    }

    if (-not $ipv6AddressesWithInterface) {
        throw "未找到有效的公网 IPv6 地址。"
    }

    foreach ($ipv6AddressInfo in $ipv6AddressesWithInterface) {
        Log-Message "网卡 '$($ipv6AddressInfo.InterfaceAlias)' 获取到公网 IPv6 地址: $($ipv6AddressInfo.IPAddress)"
    }

    # 仍然选择第一个地址作为返回值,但保留了记录所有地址的逻辑
    return $ipv6AddressesWithInterface[0].IPAddress 
}


# 函数:获取当前 DNS 记录
function Get-CurrentDNSRecord {
    $uri = "https://api.cloudflare.com/client/v4/zones/$zoneId/dns_records/$recordId"
    $headers = @{ Authorization = "Bearer $apiToken" }
    try {
        $response = Invoke-RestMethod -Uri $uri -Method Get -Headers $headers
        if ($response.success) {
            return $response.result.content
        } else {
            throw "获取 DNS 记录失败: $($response.errors | ConvertTo-Json)"
        }
    }
    catch {
        throw "获取当前 DNS 记录失败: $_"
    }
}

# 函数:更新 DNS 记录
function Update-DNSRecord {
    param(
        [string]$ipv6Address
    )
    $uri = "https://api.cloudflare.com/client/v4/zones/$zoneId/dns_records/$recordId"
    $headers = @{
        "Content-Type"  = "application/json"
        Authorization = "Bearer $apiToken"
    }
    $body = @{
        name    = $domain
        ttl     = 60
        content = $ipv6Address
        type    = "AAAA"
    } | ConvertTo-Json
    try {
        $response = Invoke-RestMethod -Uri $uri -Method Patch -Headers $headers -Body $body
        if ($response.success) {
            $message = "成功更新 DNS 记录: $domain -> $ipv6Address"
            Log-Message $message
            Send-TelegramMessage $message
        } else {
            $message = "更新 DNS 记录失败: $($response.errors | ConvertTo-Json)"
            Log-Message $message
            Send-TelegramMessage "Windows DDNS: $message" #  错误信息前缀
        }
    }
    catch {
        $message = "更新 DNS 记录失败: $_"
        Log-Message $message
        Send-TelegramMessage "Windows DDNS: $message"
    }
}


# 脚本主逻辑
Log-Message "脚本开始执行"

try {
    $ipv6Address = Get-PublicIPv6Address 
    $currentDNSRecord = Get-CurrentDNSRecord
    Log-Message "当前 $domain 的 DNS 记录内容: $currentDNSRecord"

    if ($currentDNSRecord -ne $ipv6Address) {
        Log-Message "DNS 记录与本机 IPv6 地址不一致,准备更新 DNS 记录。"
        Update-DNSRecord -ipv6Address $ipv6Address
    } else {
        Log-Message "DNS 记录与本机 IPv6 地址一致,无需更新。"
    }
}
catch {
    $message = "Windows DDNS: $_" #  错误信息前缀
    Send-TelegramMessage $message
    Log-Message $message
}

Log-Message "脚本执行结束"

按 win + r 然后输入 taskschd.msc 创建一个新的计划任务,从而实现开机未登录和登录状态定期执行,这样就可以通过域名在外面访问家里的迷你电脑了。