Hacktoberfest 2026: the issues maintainers tagged for October, open and beginner-friendly. Browse Hacktoberfest issues

Fix the PowerShell script (via Prompt Engineering)

Open
#17 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
5/5
Estimated time
Over a week
Newbie friendliness
25/100
Issue type
Feature
Clarity
Needs clarification
Activity status
Stale
Tech stack
powershell, shell
Domain
cli

Research direction

Compare the existing duckdns.sh with the proposed duckdns.ps1, starting with the DuckDNS API documentation and the generated functions shown in the issue. Validate the translation function by function on Windows, then complete and verify equivalent subcommands and behavior; done means a usable PowerShell implementation rather than the currently incomplete draft.

Written by the indexing model from the issue text.

Description

enhancement good first issue hacktoberfest up-for-grabs

Problem

We only have this for POSIX systems. It would be great to have for Windows

https://github.com/BeyondCodeBootcamp/DuckDNS.sh/blob/main/duckdns.sh

Who can solve it?

Anyone with a Windows computer and scripting or programming experience.

Solution

Get GPT to translate and check the results function-by-function as duckdns.ps1

Here's what GPT came up with so far. It's not correct, but it gets a lot of the boilerplate out of the way. Or at least it feels like it does.

$ErrorActionPreference = "Stop"

# DuckDNS API docs: https://duckdns.org/spec.jsp

$DUCKDNS_SH_COMMAND = $Env:DUCKDNS_SH_COMMAND
if (-not $DUCKDNS_SH_COMMAND) {
    $DUCKDNS_SH_COMMAND = $MyInvocation.MyCommand.Path
}

$DUCKDNS_SH_SUBCOMMAND = $args[0]
$DUCKDNS_SH_SUBDOMAIN = $args[1]
$DUCKDNS_SH_ARG_1 = $args[2]
$DUCKDNS_SH_ARG_2 = $args[3]

function cmd_auth {
    if (-not $DUCKDNS_SH_SUBDOMAIN) {
        Write-Output "You must provide a subdomain to authorize."
        Write-Output "For reference, you already have tokens for:"
        fn_list
        return
    }

    if (Test-Path -Path "~/.config/duckdns.sh/$DUCKDNS_SH_SUBDOMAIN.env") {
        Write-Output "'$DUCKDNS_SH_SUBDOMAIN.duckdns.org' is already ready to update"
        Write-Output "(to delete: 'rm ~/.config/duckdns.sh/${DUCKDNS_SH_SUBDOMAIN}.env')"
    } else {
        fn_read_token
        Write-Output "Created '~/.config/duckdns.sh/$DUCKDNS_SH_SUBDOMAIN.env'"
    }
}

function cmd_clear {
    fn_require_subdomain
    fn_duckdns_clear
}

function cmd_ip {
    fn_require_subdomain
    fn_require_dig

    Write-Output "dig +short A $DUCKDNS_SH_SUBDOMAIN.duckdns.org"
    $my_domain_ipv4 = (dig +short A "$DUCKDNS_SH_SUBDOMAIN.duckdns.org")
    Write-Output "$DUCKDNS_SH_SUBDOMAIN.duckdns.org A $($my_domain_ipv4 -join ', ')"
    Write-Output ""

    Write-Output "dig +short AAAA $DUCKDNS_SH_SUBDOMAIN.duckdns.org"
    $my_domain_ipv6 = (dig +short AAAA "$DUCKDNS_SH_SUBDOMAIN.duckdns.org")
    Write-Output "$DUCKDNS_SH_SUBDOMAIN.duckdns.org AAAA $($my_domain_ipv6 -join ', ')"
    Write-Output ""
}

function cmd_launcher_install {
    fn_require_subdomain
    fn_require_curl

    if (-not (Test-Path -Path "$Env:UserProfile\.config\envman\PATH.env")) {
        Invoke-Expression ((Invoke-WebRequest -Uri 'https://webi.sh/serviceman').Content)
        . "$Env:UserProfile\.config\envman\PATH.env"
    }

    if (Test-Path -Path "C:\Windows\System32\launchctl.exe") {
        serviceman add --user --name "sh.duckdns.$DUCKDNS_SH_SUBDOMAIN" -- "$DUCKDNS_SH_COMMAND" run $DUCKDNS_SH_SUBDOMAIN
        return
    }

    if (Test-Path -Path "C:\Windows\System32\systemctl.exe") {
        Write-Output ""
        Write-Output "Running command: & 'C:\Windows\System32\serviceman.exe' add --system --path=\"$Env:Path\" --username $Env:UserName --name ""${DUCKDNS_SH_SUBDOMAIN}.duckdns-sh"" -- ""$DUCKDNS_SH_COMMAND"" run $DUCKDNS_SH_SUBDOMAIN"
        Write-Output ""
        & 'C:\Windows\System32\serviceman.exe' add --system --path=$Env:Path --username $Env:UserName --name "${DUCKDNS_SH_SUBDOMAIN}.duckdns-sh" -- "$DUCKDNS_SH_COMMAND" run $DUCKDNS_SH_SUBDOMAIN
        Write-Output ""
        Write-Output "Running command: & 'C:\Windows\System32\systemctl.exe' restart systemd-journald"
        Write-Output ""
        & 'C:\Windows\System32\systemctl.exe' restart systemd-journald
        return
    }

    Write-Output "'launchctl' (macOS) and 'systemd' (Linux) are the only currently supported launchers"
}

function cmd_launcher_uninstall {
    fn_require_subdomain

    if (Test-Path -Path "$Env:UserProfile\Library\LaunchAgents\sh.duckdns.$DUCKDNS_SH_SUBDOMAIN.plist") {
        Write-Output "launchctl unload -w $Env:UserProfile\Library\LaunchAgents\sh.duckdns.$DUCKDNS_SH_SUBDOMAIN.plist"
        & "launchctl unload -w $Env:UserProfile\Library\LaunchAgents\sh.duckdns.$DUCKDNS_SH_SUBDOMAIN.plist"
        Write-Output "Disabled login launcher."
    } elseif (Test-Path -Path "C:\Windows\System32\systemctl.exe") {
        Write-Output "Running command: & 'C:\Windows\System32\systemctl.exe' stop ""${DUCKDNS_SH_SUBDOMAIN}.duckdns-sh"""
        & 'C:\Windows\System32\systemctl.exe' stop "${DUCKDNS_SH_SUBDOMAIN}.duckdns-sh"
        Write-Output "Running command: & 'C:\Windows\System32\systemctl.exe' disable ""${DUCKDNS_SH_SUBDOMAIN}.duckdns-sh"""
        & 'C:\Windows\System32\systemctl.exe' disable "${DUCKDNS_SH_SUBDOMAIN}.duckdns-sh"
        Write-Output "Disabled system launcher."
    } else {
        Write-Output "'launchctl' (macOS) and 'systemd' (Linux) are the only currently supported launchers"
    }
}

function cmd_list {
    Write-Output ""
    fn_list
}

function cmd_myip {
    fn_require_curl

    Write-Output "Invoke-WebRequest -Uri 'https://api.ipify.org?format=text'"
    $my_current_ipv4 = (Invoke-WebRequest -Uri 'https://api.ipify.org?format=text').Content
    Write-Output "IPv4 $($my_current_ipv4)"
    Write-Output ""

    Write-Output "Invoke-WebRequest -Uri 'https://api64.ipify.org?format=text'"
    $my_current_ipv6 = (Invoke-WebRequest -Uri 'https://api64.ipify.org?format=text').Content
    if ($my_current_ipv4 -eq $my_current_ipv6) {
        $my_current_ipv6 = $null
    }
    Write-Output "IPv6 $($my_current_ipv6)"
}

function cmd_run {
    fn_require_subdomain
    fn_require_env
    fn_require_curl
    fn_require_dig

    $my_minutes = 1
    $my_wait = ($my_minutes * 60)
    while ($true) {
        fn_update_ips
        Write-Output ""
        Write-Output ""
        Write-Output "Waiting $my_minutes minute(s) to check '$DUCKDNS_SH_SUBDOMAIN' again..."
        Start-Sleep -Seconds $my_wait
        Write-Output ""
    }
}

function cmd_set {
    fn_require_subdomain
    fn_require_env
    fn_require_curl

    if ($DUCKDNS_SH_ARG_2) {
        fn_duckdns_update $DUCKDNS_SH_ARG_1 $DUCKDNS_SH_ARG_2
    } else {
        switch -wildcard ($DUCKDNS_SH_ARG_1) {
            *:[a-fA-F0-9]* {
                fn_duckdns_update '' $DUCKDNS_SH_ARG_1
            }
            *[0-9].[0-9]* {
                fn_duckdns_update $DUCKDNS_SH_ARG_1 ''
            }
            default {
                Write-Output "'$DUCKDNS_SH_ARG_1' is not a valid IPv4 or IPv6 address"
            }
        }
    }
}

function cmd_update {
    fn_require_subdomain
    fn_require_env
    fn_require_curl
    fn_require_dig
    fn_update_ips
}

function cmd_version {
    $my_year = '2023'
    $my_version = 'v1.0.3'
    $my_date = '2023-01-15 00:49:52 +0000'

    Write-Output "DuckDNS.sh $my_version ($my_date)"
    Write-Output "Copyright $my_year AJ ONeal"
}

function fn_check_env {
    if (-not (Test-Path -Path "~/.config/duckdns.sh/$DUCKDNS_SH_SUBDOMAIN.env")) {
        return $false
    }

    . "~/.config/duckdns.sh/$DUCKDNS_SH_SUBDOMAIN.env"
    if (-not $Env:DUCKDNS_TOKEN) {
        return $false
    }

    return $true
}

function fn_duckdns_clear {
    . "~/.config/duckdns.sh/$DUCKDNS_SH_SUBDOMAIN.env"

    Write-Output "Invoke-WebRequest -Uri 'https://www.duckdns.org/update?domains=$DUCKDNS_SH_SUBDOMAIN&token=****&clear=true'"

    Write-Output "Clearing IP address(es)..."

    Invoke-WebRequest -Uri "https://www.duckdns.org/update?domains=$DUCKDNS_SH_SUBDOMAIN&token=$Env:DUCKDNS_TOKEN&clear=true" -UseBasicParsing
}

function fn_duckdns_update {
    param(
        [string]$my_ipv4,
        [string]$my_ipv6
    )

    $my_ipv4_param = ""
    if ($my_ipv4) {
        $my_ipv4_param = "&ip=$my_ipv4"
    }

    $my_ipv6_param = ""
    if ($my_ipv6) {
        $my_ipv6_param = "&ipv6=$my_ipv6"
    }

    Write-Output "Invoke-WebRequest -Uri 'https://www.duckdns.org/update?domains=$DUCKDNS_SH_SUBDOMAIN&token=****$my_ipv4_param$my_ipv6_param'"

    if ($my_ipv6_param) {
        if ($my_ipv4_param) {
            Write-Output "Updating IPv4 ($my_ipv4) and IPv6 ($my_ipv6)..."
        } else {
            Write-Output "Updating IPv6 ($my_ipv6)..."
        }
    } else {
        if ($my_ipv4_param) {
            Write-Output "Updating IPv4 ($my_ipv4)..."
        } else {
            Write-Output "at least one of ipv4 or ipv6 is required to update"
            return
        }
    }

    . "~/.config/duckdns.sh/$DUCKDNS_SH_SUBDOMAIN.env"
    Invoke-WebRequest -Uri "https://www.duckdns.org/update?domains=$DUCKDNS_SH_SUBDOMAIN&token=$Env:DUCKDNS_TOKEN$my_ipv4_param$my_ipv6_param" -UseBasicParsing

    Write-Output ""
}

function fn_list {
    Write-Output "~/.config/duckdns.sh/:"

    if (-not (Test-Path -Path "~/.config/duckdns.sh")) {
        Write-Output "(directory does not exist)"
    }

    Get-ChildItem "~/.config/duckdns.sh" | ForEach-Object {
        $my_domainenv = $_.Name -replace '.env$'

        if ($my_domainenv -eq '*') {
            Write-Output "    (no subdomains have been configured)"
        } else {
            Write-Output "    $my_domainenv"
        }
    }

    Write-Output ""
}

function fn_read_secret {
    param(
        [string]$my_prompt
    )

    $password = Read-Host -Prompt $my_prompt -AsSecureString
    $password = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($password))
    return $password
}

function fn_read_token {
    $tokenPath = "~/.config/duckdns.sh/$DUCKDNS_SH_SUBDOMAIN.env"

    if (-not (Test-Path -Path $tokenPath)) {
        New-Item -Path $tokenPath -ItemType File
    }

    Write-Output "(the token will NOT BE SHOWN - just HIT ENTER after you paste it)"
    $my_token = fn_read_secret "Duck DNS Token>"

    Add-Content -Path $tokenPath -Value "DUCKDNS_TOKEN=$my_token"
}

function fn_require_curl {
    if (-not (Test-Path -Path "C:\Windows\System32\curl.exe")) {
        Write-Output "command 'curl' not found: please install 'curl'"
        throw "Curl not found."
    }
}

function fn_require_dig {
    if (-not (Test-Path -Path "C:\Windows\System32\dig.exe")) {
        Write-Output "command 'dig' not found: please install 'dig' (part of 'dnsutils')"
        throw "Dig not found."
    }
}

function fn_require_env {
    if (-not (Test-Path -Path "~/.config/duckdns.sh/$DUCKDNS_SH_SUBDOMAIN.env")) {
        Write-Output "Missing '~/.config/duckdns.sh/$DUCKDNS_SH_SUBDOMAIN.env'"
        throw "Environment file missing."
    }

    . "~/.config/duckdns.sh/$DUCKDNS_SH_SUBDOMAIN.env"
    if (-not $Env:DUCKDNS_TOKEN) {
        Write-Output "Missing 'DUCKDNS_TOKEN=<uuid>' from '~/.config/duckdns.sh/$DUCKDNS_SH_SUBDOMAIN.env'"
        throw "DUCKDNS_TOKEN not found in environment."
    }
}

function fn_require_subdomain {
    if (-not $DUCKDNS_SH_SUBDOMAIN) {
        Write-Output "Missing <subdomain> argument. Try one of these in:"
        fn_list
        throw "Missing subdomain argument."
    }
}

function fn_update_ips {
    Write-Output "Invoke-WebRequest -Uri 'https://api.ipify.org?format=text'"
    $my_domain_ipv4 = (Invoke-WebRequest -Uri 'https://api.ipify.org?format=text').Content
    Write-Output "IPv4 $($my_domain_ipv4)"
    Write-Output ""

    Write-Output "Invoke-WebRequest -Uri 'https://api64.ipify.org?format=text'"
    $my_domain_ipv6 = (Invoke-WebRequest -Uri 'https://api64.ipify.org?format=text').Content
    if ($my_domain_ipv4 -eq $my_domain_ipv6) {
        $my_domain_ipv6 = $null
    }
    Write-Output "IPv6 $($my_domain_ipv6)"
    Write-Output ""

    # if either ip changed to be empty

And then it stopped generating.

Function fn_update_ips {
    Write-Output "dig +short A '${DUCKDNS_SH_SUBDOMAIN}.duckdns.org'"
    $my_domain_ipv4 = (dig +short A "${DUCKDNS_SH_SUBDOMAIN}.duckdns.org")
    Write-Output "${DUCKDNS_SH_SUBDOMAIN}.duckdns.org A ${my_domain_ipv4:-(NONE)}"
    Write-Output ""

    Write-Output "dig +short AAAA '${DUCKDNS_SH_SUBDOMAIN}.duckdns.org'"
    $my_domain_ipv6 = (dig +short AAAA "${DUCKDNS_SH_SUBDOMAIN}.duckdns.org")
    Write-Output "${DUCKDNS_SH_SUBDOMAIN}.duckdns.org AAAA ${my_domain_ipv6:-(NONE)}"
    Write-Output ""

    Write-Output "curl -fsSL 'https://api.ipify.org?format=text'"
    $my_current_ipv4 = (curl --max-time 5.5 -Uri 'https://api.ipify.org?format=text')
    Write-Output "IPv4 ${my_current_ipv4:-(NONE)}"
    Write-Output ""

    Write-Output "curl -fsSL 'https://api64.ipify.org?format=text'"
    $my_current_ipv6 = (curl --max-time 5.5 -Uri 'https://api64.ipify.org?format=text')
    if ($my_current_ipv4 -eq $my_current_ipv6) {
        $my_current_ipv6 = ""
    }
    Write-Output "IPv6 ${my_current_ipv6:-(NONE)}"
    Write-Output ""

    # if either IP changed to be empty, clear both
    if ($my_current_ipv4 -ne $my_domain_ipv4) {
        if (-not $my_current_ipv4) {
            fn_duckdns_clear
            $my_domain_ipv4 = ""
            $my_domain_ipv6 = ""
        }
    }
    if ($my_current_ipv6 -ne $my_domain_ipv6) {
        if (-not $my_current_ipv6) {
            fn_duckdns_clear
            $my_domain_ipv4 = ""
            $my_domain_ipv6 = ""
        }
    }

    # Note: at least one of the IPv4 or IPv6 *must* exist
    # (otherwise we wouldn't even be able to get an empty response)
    if ($my_current_ipv4 -ne $my_domain_ipv4) {
        if ($my_current_ipv6 -ne $my_domain_ipv6) {
            fn_duckdns_update $my_current_ipv4 $my_current_ipv6
        }
        else {
            fn_duckdns_update $my_current_ipv4 ""
        }
    }
    else {
        if ($my_current_ipv6 -ne $my_domain_ipv6) {
            fn_duckdns_update "" $my_current_ipv6
        }
        else {
            Write-Output "No change detected."
        }
    }
}

Function fn_help {
    Write-Output ""
    Write-Output "USAGE"
    Write-Output "    duckdns.sh <subcommand> [arguments...]"
    Write-Output ""
    Write-Output "SUBCOMMANDS"
    Write-Output "    myip                         - show this device's ip(s)"
    Write-Output "    ip <subdomain>               - show subdomain's ip(s)"
    Write-Output ""
    Write-Output "    list                         - show subdomains"
    Write-Output "    auth <subdomain>             - add Duck DNS token"
    Write-Output "    update <subdomain>           - update subdomain to device ip"
    Write-Output "    set <subdomain> <ip> [ipv6]  - set ipv4 and/or ipv6 explicitly"
    Write-Output "    clear <subdomain>            - unset ip(s)"
    Write-Output "    run <subdomain>              - check ip and update every 5m"
    Write-Output "    enable <subdomain>           - enable on boot (Linux) or login (macOS)"
    Write-Output "    disable <subdomain>          - disable on boot or login"
    Write-Output ""
    Write-Output "    help                         - show this menu"
    Write-Output "    version                      - show version and exit"
    Write-Output ""
}

Function main {
    switch ($DUCKDNS_SH_SUBCOMMAND) {
        "myip" { cmd_myip }
        "ip" { cmd_ip }
        "list" { cmd_list }
        "auth" { cmd_auth }
        "update" { cmd_update }
        "set" { cmd_set }
        "clear" { cmd_clear }
        "run" { cmd_run }
        "enable" { cmd_launcher_install }
        "disable" { cmd_launcher_uninstall }
        "help" { fn_help }
        "version" { cmd_version }
        default {
            fn_help
            exit 1
        }
    }
}

main
Dominant language
Shell
Stars
32
Forks
3
PR merge metrics
No merged PRs in 30d

Getting set up

This project ships no dev container, Dockerfile or contributing guide, so setting up is up to you: start from its README, and see our first-contribution guide for the general steps.

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from BeyondCodeBootcamp/DuckDNS.sh

All issues in BeyondCodeBootcamp/DuckDNS.sh

Similar issues

More Shell/Bash issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.