Invoke-HAService.ps1

function Invoke-HAService {
    <#
    .SYNOPSIS
    Call a Home Assistant service against an entity.
 
    .DESCRIPTION
    Wraps POST /api/services/<domain>/<service>. The domain is derived from
    the entity id (everything before the first dot). Connection details are
    read from ~/.pwrhass/config.json (Connect-HomeAssistant first).
 
    .PARAMETER EntityId
    Target entity, e.g. 'light.den_bookshelve' or 'cover.kitchen_vent'.
 
    .PARAMETER Service
    Service name on the entity's domain (e.g. 'turn_on', 'set_cover_position').
    Defaults to 'turn_on'; pass -Off for the matching 'turn_off' shorthand.
 
    .PARAMETER Data
    Extra body fields merged into the request payload alongside entity_id.
    Example: @{ position = 100 } for cover.set_cover_position.
 
    .PARAMETER Off
    Shorthand: when set and -Service is unset, calls 'turn_off' instead of 'turn_on'.
 
    .EXAMPLE
    Invoke-HAService -EntityId light.den_bookshelve
 
    .EXAMPLE
    Invoke-HAService -EntityId light.den_bookshelve -Off
 
    .EXAMPLE
    Invoke-HAService -EntityId cover.kitchen_vent `
                     -Service set_cover_position `
                     -Data @{ position = 100 }
    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Low')]
    param(
        [Parameter(Mandatory, Position = 0)]
        [ValidatePattern('^[a-z_]+\.[a-z0-9_]+$')]
        [string]$EntityId,

        [Parameter(Position = 1)]
        [string]$Service,

        [hashtable]$Data,

        [switch]$Off
    )

    if (-not $Service) {
        $Service = if ($Off) { 'turn_off' } else { 'turn_on' }
    }

    $cfg = Get-HAConfig
    $domain = $EntityId.Split('.')[0]
    $uri = "$($cfg.BaseUrl)/api/services/$domain/$Service"

    $payload = @{ entity_id = $EntityId }
    if ($Data) {
        foreach ($k in $Data.Keys) { $payload[$k] = $Data[$k] }
    }
    $body = $payload | ConvertTo-Json -Compress

    if (-not $PSCmdlet.ShouldProcess($EntityId, "$domain.$Service")) { return }

    Write-Verbose "POST $uri body=$body"
    $headers = @{
        Authorization  = "Bearer $($cfg.Token)"
        'Content-Type' = 'application/json'
    }
    $params = @{
        Method     = 'Post'
        Uri        = $uri
        Headers    = $headers
        Body       = $body
        TimeoutSec = 30
    }
    if ($cfg.SkipCertificateCheck) { $params.SkipCertificateCheck = $true }

    Invoke-RestMethod @params
}