KnowIT.Interworx.psm1


#region === Source functions ===

### Source file: 'Connect-Interworx.ps1' ###
function Connect-Interworx {

    [CmdletBinding(DefaultParameterSetName = 'Credential')]
    param(
        [Parameter(Mandatory, Position = 0)]
        [ValidateNotNullOrEmpty()]
        [Alias('Host')]
        [string]$Server,

        [Parameter(Mandatory, ParameterSetName = 'ApiKey')]
        [ValidateNotNullOrEmpty()]
        [securestring]$ApiKey,

        [Parameter(Mandatory, ParameterSetName = 'Credential')]
        [pscredential]$Credential
    )

    try {
        Update-CallerPreference $PSCmdlet

        $Server = $Server.TrimEnd('/')
        if($Server -notmatch ':\d+') {
            $Server = "${Server}:2443"
        }
        if(!$Server.StartsWith('http')) {
            $Server = "https://$Server"
        }

        $newConnection = switch ($PSCmdlet.ParameterSetName) {
            'ApiKey' { @{
                Method     = 'ApiKey'
                Server     = $Server
                Credential = [pscredential]::new('apikey', $ApiKey)
            } }
            'Credential' { @{
                Method     = 'Credential'
                Server     = $Server
                Credential = $Credential
            } }
            default {
                throw "Invalid parameter set: $($PSCmdlet.ParameterSetName)"
            }
        }

        Write-Verbose "Establece conexión con Interworx '$($newConnection.Server)' mediante [$($newConnection.Method)]" -Verbose
        $script:IworxConnection = $newConnection
    }
    catch {
        $PSCmdlet.WriteError($_)
    }
}

### Source file: 'Get-InterworxConnection.ps1' ###
function Get-InterworxConnection {

    [CmdletBinding()]
    param(

    )

    try {
        Update-CallerPreference $PSCmdlet

        [PSCustomObject]$script:IworxConnection
    }
    catch {
        $PSCmdlet.WriteError($_)
    }
}

### Source file: 'Get-NodeworxDnsZone.ps1' ###
function Get-NodeworxDnsZone {

    [CmdletBinding()]
    param(
        [Parameter(Position = 0)]
        [ValidateNotNullOrEmpty()]
        [Alias('DomainPattern')]
        [string]$Domain
    )

    try {
        Update-CallerPreference $PSCmdlet

        Invoke-Nodeworx 'DnsZone' -Action 'listZones' |
        Map-Object {
            if($_.is_template -eq 1 -or
                ($Domain -and $_.domain -notmatch $Domain)) {
                return
            }

            [PSCustomObject]@{
                PSTypeName   = 'KnowIT.Nodeworx.DnsZone'
                ZoneId       = $_.zone_id
                Domain       = $_.domain
                MasterDomain = $_.master_domain
                DomainType   = $_.domain_type
                Suspended    = $_.is_suspended -as [bool]
            }
        }
    }
    catch {
        $PSCmdlet.WriteError($_)
    }
}

### Source file: 'Invoke-Nodeworx.ps1' ###
function Invoke-Nodeworx {

    [CmdletBinding()]
    param(

        [Parameter(Mandatory)]
        [string]$Controller,

        [Parameter(Mandatory)]
        [string]$Action,

        [hashtable]$Params = @{},

        # Si se especifica, devuelve la respuesta XML sin procesar
        [switch]$AsXmlString
    )

    try {
        Update-CallerPreference $PSCmdlet

        $controller = '/nodeworx/' + $Controller.TrimStart('/')
        $request = CreateIworxRequest $controller $Action $Params

        $response = Invoke-WebRequest @request -UseBasicParsing -Verbose:$false
        $content = $response.Content

        if($AsXmlString) {
            return $content
        }
        ParseIworxResponse $content
    }
    catch {
        $PSCmdlet.WriteError($_)
    }
}

### Source file: 'IworxApi.ps1' ###
function CreateIworxRequest ([string]$Controller, [string]$Action, [hashtable]$Params, [string]$Domain)
{
    if(!$script:IworxConnection) {
        throw "No se ha establecido una conexión a Interworx. Ejecuta el comando 'Connect-Interworx'."
    }

    $cred = $script:IworxConnection.Credential.GetNetworkCredential()
    $key = switch ($script:IworxConnection.Method) {
        'ApiKey' {
            if($Domain) {
                @{ apikey = $cred.Password; domain = $Domain }
            }
            else { $cred.Password }
        }
        'Credential' {
            $credKey = @{ email = $cred.UserName; password = $cred.Password }
            if($Domain) {
                $credKey.domain = $Domain
            }
            $credKey
        }
        default {
            throw "Método de autenticación incorrecto: $($script:IworxConnection.Method)"
        }
    }

    $body = @"
<?xml version="1.0" encoding="UTF-8"?>
<methodCall>
    <methodName>iworx.route</methodName>
    <params>
        <param><value>$(ConvertToXmlRpcValue $key)</value></param>
        <param><value><string>$Controller</string></value></param>
        <param><value><string>$Action</string></value></param>
        <param><value>$(ConvertToXmlRpcStruct $Params)</value></param>
    </params>
</methodCall>
"@

    Write-Debug "Request body: $body"

    return @{
        Method      = 'POST'
        Uri         = $script:IworxConnection.Server + '/xmlrpc'
        Body        = $body
        ContentType = 'text/xml; charset=UTF-8'
    }
}

function ParseIworxResponse ([xml]$Response)
{
    if($Response.methodResponse.fault) {
        $errorCode = $response.methodResponse.fault.value.struct.member.Where({ $_.name -eq 'faultCode' }).value.int
        $errorString = $response.methodResponse.fault.value.struct.member.Where({ $_.name -eq 'faultString' }).value.string
        throw "API Error - Code [$errorCode]: $errorString."
    }

    $status = $Response.methodResponse.params.param.value.struct.member.Where({ $_.name -eq 'status' }).value.int
    $payload = $Response.methodResponse.params.param.value.struct.member.Where({ $_.name -eq 'payload' }).value

    if($status -gt 0) {
        throw "API Error - Status code [$status] $($payload.string)"
    }

    ParseXmlRpcNode $payload
}

### Source file: 'KnowIT.ModuleHelpers.ps1' ###
function Update-CallerPreference {
    # https://devblogs.microsoft.com/scripting/weekend-scripter-access-powershell-preference-variables/
    param(
        [ValidateNotNull()]
        [PSTypeName('System.Management.Automation.PSScriptCmdlet')]$ScriptCmdlet = (Get-Variable PSCmdlet -Scope 1 -ValueOnly),

        [ValidateSet('ErrorAction', 'Warning', 'Verbose', 'Debug', 'Information', 'Progress', 'Confirm', 'WhatIf')]
        [string[]]$Skip
    )

    $commonParameters = 'ErrorAction', 'Warning', 'Verbose', 'Debug', 'Information', 'Progress', 'Confirm', 'WhatIf'

    $invocation = $ScriptCmdlet.MyInvocation
    $commandDebug = $invocation.BoundParameters.ContainsKey('Debug')

    Write-Debug "Updating [$($invocation.MyCommand)] Preference variables:" -Debug:$commandDebug
    foreach($p in $commonParameters) {
        if($invocation.BoundParameters.ContainsKey($p)) {
            continue
        }
        $var = "${p}Preference"

        if($p -eq 'ErrorAction') {
            $val = 'Stop'
            $scope = 'Forced'
        }
        elseif($p -in $Skip) {
            $val = Get-Variable -Scope Global -Name $var -ValueOnly
            $scope = 'Global'
        }
        else {
            $val = $ScriptCmdlet.GetVariableValue($var)
            $scope = 'Caller'
        }
        Write-Debug " (From $scope scope) $var = $val " -Debug:$commandDebug
        Set-Variable -Scope 1 -Name $var -Value $val
    }
}

function Map-Object {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseApprovedVerbs', '', Justification = 'Internal functions')]
    param([scriptblock]$ScriptBlock)

begin {
    $code = "& { process { $ScriptBlock } }"
    $pipeline = [scriptblock]::Create($code).GetSteppablePipeline()
    $pipeline.Begin($true)
}
process {
    $pipeline.Process($_)
}
end {
    $pipeline.End()
}
}

### Source file: 'XmlRpc.ps1' ###
function ConvertToXmlRpcValue ($Value)
{
    switch ($Value.GetType().Name) {
        'String'  { "<string>$Value</string>" }
        'Int32'   { "<int>$Value</int>" }
        'Boolean' { "<boolean>$([int]$Value)</boolean>" }
        'Hashtable' { ConvertToXmlRpcStruct $Value }
        'OrderedDictionary' { ConvertToXmlRpcStruct $Value }
        default { throw "Unsupported parameter type: [$_] $Value" }
    }
}

function ConvertToXmlRpcStruct ($Params)
{
    if($Params.Count -eq 0) {
        return "<struct/>"
    }

    $members = foreach($key in $Params.Keys) {
        $value = ConvertToXmlRpcValue $Params[$key]
        "<member><name>$key</name><value>$value</value></member>"
    }
    "<struct>`n$($members -join "`n")`n</struct>"
}

function ParseXmlRpcNode ([Xml.XmlElement]$Node)
{
    if($Node.struct) {
        $result = @{}

        foreach ($member in $Node.struct.member) {
            $name = $member.name
            $valueNode = $member.value

            $result[$name] = ParseXmlRpcNode $valueNode
        }
        return [PSCustomObject]$result
    }

    if($Node.array) {
        foreach ($item in $Node.array.data.value) {
            ParseXmlRpcNode $item
        }
        return
    }

    if($Node.string)  { return $Node.string }
    if($Node.int)     { return [int]$Node.int }
    if($Node.i4)      { return [int]$Node.i4 }
    if($Node.boolean) { return [bool]([int]$Node.boolean) }

    return $Node.InnerText
}

#endregion