PSXmlRpcClient.psm1

function Invoke-XmlRpcRequest {
    <#
    .SYNOPSIS
    发送XML-RPC请求
    .DESCRIPTION
    发送XML-RPC请求,如果服务器响应为200,则解析服务器响应;否则输出异常信息
    .PARAMETER ServerUri
    XML-RPC服务器地址
    .PARAMETER MethodName
    要调用的方法名称
    .PARAMETER Params
    要传递给方法的参数,可以为空
    .INPUTS
    uri
    string
    array
    .OUTPUTS
    object
    .EXAMPLE
    Invoke-XmlRpcRequest -Url "example.com" -MethodName "system.methodHelp" -Params "phone"
    ---------
    Description
    Calls a method "system.methodHelp("phone")" on the server example.com
    #>

    [CmdletBinding()]
    [OutputType([object])]
    param (
        [Parameter(Mandatory=$true)]
        [uri]$ServerUri,

        [Parameter(Mandatory=$true)]
        [string]$MethodName,

        [array]$Params
    )

    $body=$(ConvertTo-XmlRpcPayload -MethodName $MethodName -Params $Params)

    # ================================
    # works well
    $webRequest=[System.Net.HttpWebRequest]::CreateHttp($ServerUri)
    $webRequest.ServicePoint.Expect100Continue=$false
    $webRequest.AllowAutoRedirect=$true
    $webRequest.KeepAlive=$false
    $webRequest.ProtocolVersion=[System.Net.HttpVersion]::Version11
    $webRequest.Method="POST"

    $bytes=[System.Text.Encoding]::UTF8.GetBytes($body)
    $req=$webRequest.GetRequestStream()
    $req.Write($bytes, 0, $bytes.Length)
    $req.Dispose()

    $resp=[System.Net.HttpWebResponse]$webRequest.GetResponse()
    $stream=$resp.GetResponseStream()
    $sr=New-Object System.IO.StreamReader($stream)
    $response=$sr.ReadToEnd()
    $sr.Dispose()
    $stream.Dispose()
    $resp.Dispose()
    # ================================

    # 使用“2”个参数调用“UploadString”时发生异常:“服务器提交了协议冲突. Section=ResponseStatusLine”
    # 有个比较奇怪的现象:
    # 1、连续两次请求的第二次必出现该问题
    # 2、与两次请求的先后顺序无关
    # 3、连续四次请求的第二次、第四次必出现该问题
    # $tmp=[System.Net.ServicePointManager]::Expect100Continue
    # [System.Net.ServicePointManager]::Expect100Continue = $false
    # $client = New-Object System.Net.WebClient
    # try {
    # $client.Encoding = [System.Text.Encoding]::UTF8
    # $response = $client.UploadString($ServerUri, $body)
    # }
    # finally {
    # [System.Net.ServicePointManager]::Expect100Continue=$tmp
    # $client.Dispose()
    # }

    # Invoke-RestMethod : 417 - Expectation Failed
    # $request=@{uri=$ServerUri;
    # Method="POST";
    # Headers=@{Authorization="Basic <base64-encoded-credentials>"; "Content-Type"="text/xml"}
    # Body=$body}
    # $response=$(Invoke-RestMethod -UseBasicParsing @request -DisableKeepAlive)

    # Invoke-RestMethod : 417 - Expectation Failed
    # $response=$(Invoke-RestMethod -Uri $ServerUri -UseBasicParsing -Method "POST" -Body $body)

    # Invoke-WebRequest : 417 - Expectation Failed
    # $response=$(Invoke-WebRequest -UseBasicParsing -Uri $ServerUri -DisableKeepAlive -Method "POST" -Body $body -ContentType "text/xml")

    if ($response) {
        ConvertFrom-Xml -Xml $response
    }
}

function ConvertTo-XmlRpcPayload {
    <#
    .SYNOPSIS
    将方法名和参数转换为XML-RPC格式的xml
    .DESCRIPTION
    将方法名和参数转换为XML-RPC格式的xml
    .PARAMETER MethodName
    要调用的方法名称
    .PARAMETER Params
    要传递给方法的参数,可以为空
    .INPUTS
    string
    array
    .OUTPUTS
    string
    .EXAMPLE
    ConvertTo-XmlRpcPayload -MethodName "system.methodHelp" -Params "phone"
    ---------
    Description
    Convert method "system.methodHelp("phone")" to XML in XML-RPC request
    #>

    [CmdletBinding()]
    [OutputType([string])]
    param (
        [Parameter(Mandatory=$true)]
        [string]$MethodName,

        [array]$Params
    )

    $requestXml="<?xml version='1.0'?><methodCall><methodName>{0}</methodName><params>{1}</params></methodCall>"

    $paramsBuilder=[System.Text.StringBuilder]::new()
    $Params | ForEach-Object {
        if ($_) {
            [void]$paramsBuilder.AppendFormat("<param><value>{0}</value></param>", $(ConvertTo-XmlRpcType -InputObject $_))
        }
    }

    return $([xml]($requestXml -f $MethodName, $paramsBuilder.ToString())).OuterXml
}

function ConvertTo-XmlRpcType {
    <#
    .SYNOPSIS
    将PowerShell类型转换为XML-RPC格式的xml
    .DESCRIPTION
    将PowerShell类型转换为XML-RPC格式的xml
    .PARAMETER InputObject
    PowerShell类型
    .INPUTS
    object
    .OUTPUTS
    string
    .EXAMPLE
    ConvertTo-XmlRpcType -InputObject 365
    #>

    [CmdletBinding()]
    [OutputType([string])]
    param (
        $InputObject
    )

    begin {
        # 在模块清单(psd1)文件中添加依赖在脚本中不好使???
        # RequiredAssemblies = @("System.Web.dll")
        [void][System.Reflection.Assembly]::LoadWithPartialName("System.Web")
    }

    process {
        # four-byte signed integer
        if ($InputObject -is [int]) {
            # return "<i4>$InputObject</i4>"
            return "<int>$InputObject</int>"
        }
        # 0 (false) or 1 (true)
        if ($InputObject -is [bool]) {
            if ($InputObject) { $boolValue=1 } else { $boolValue=0 }
            return "<boolean>$boolValue</boolean>"
        }
        # string
        if ($InputObject -is [string]) {
            return "<string>$([System.Web.HttpUtility]::HtmlEncode($InputObject))</string>"
            # return "<string>$InputObject</string>"
        }
        # double-precision signed floating point number
        if (($InputObject -is [double]) -or ($InputObject -is [float]) -or ($InputObject -is [decimal])) {
            return "<double>$InputObject</double>"
        }
        # date/time
        if ($InputObject -is [datetime]) {
            return "<dateTime.iso8601>$($InputObject.ToString('yyyyMMddTHH:mm:ss'))</dateTime.iso8601>"
        }
        # base64-encoded binary
        if (($InputObject -is [array]) -and ($InputObject.Length -gt 0) -and ($InputObject[0] -is [byte])) {
            return "<base64>$([System.Convert]::ToBase64String($InputObject))</base64>"
        }
        # <struct>s
        if ($InputObject -is [hashtable]) {
            $structBuilder=[System.Text.StringBuilder]::new()
            [void]$structBuilder.Append("<struct>")
            $InputObject.Keys | ForEach-Object {
                [void]$structBuilder.AppendFormat("<member><name>{0}</name><value>{1}</value></member>", $_, $(ConvertTo-XmlRpcType -InputObject $InputObject[$_]))
            }
            [void]$structBuilder.Append("</struct>")
            return $structBuilder.ToString()
        }
        # <array>s
        if ($InputObject -is [array]) {
            $arrayBuilder=[System.Text.StringBuilder]::new()
            [void]$arrayBuilder.Append("<array><data>")
            foreach ($obj in $InputObject) {
                [void]$arrayBuilder.AppendFormat("<value>{0}</value>", $(ConvertTo-XmlRpcType -InputObject $obj))
            }
            [void]$arrayBuilder.Append("</data></array>")
            return $arrayBuilder.ToString()
        }
        throw "$($InputObject.GetType().Name) type is not supported."
    }

    end {

    }
}

function ConvertFrom-Xml {
    <#
    .SYNOPSIS
    将XML-RPC服务器响应的xml转换为PowerShell类型
    .DESCRIPTION
    将XML-RPC服务器响应的xml转换为PowerShell类型
    .PARAMETER Xml
    XML-RPC格式的xml
    .INPUTS
    string or xml
    .OUTPUTS
    object
    .EXAMPLE
    ConvertFrom-Xml -Xml "<?xml version='1.0'?><methodResponse><params><param><value><string>South Dakota</string></value></param></params></methodResponse>"
    #>

    [CmdletBinding()]
    [OutputType([object])]
    param (
        [Parameter(Mandatory=$true)]
        $Xml
    )

    if ($Xml -is [string]) {
        $XmlDocument=[xml]$Xml
    } elseif ($Xml -is [xml]) {
        $XmlDocument=$Xml
    }

    if ($XmlDocument -isnot [System.Xml.XmlDocument]) {
        throw "Only types [string](xml format), [xml] are supported"
    }

    # normal response
    foreach ($param in $XmlDocument.methodResponse.params.param) {
        foreach ($value in $param.value) {
            ConvertFrom-XmlRpcType -XmlObject $value.FirstChild
        }
    }

    # fault response
    foreach ($fault in $XmlDocument.methodResponse.fault) {
        foreach ($value in $fault.value) {
            ConvertFrom-XmlRpcType -XmlObject $value.struct
        }
    }
}

function ConvertFrom-XmlRpcType {
    <#
    .SYNOPSIS
    将XML-RPC格式的xml转换为PowerShell类型
    .DESCRIPTION
    将XML-RPC格式的xml转换为PowerShell类型
    .PARAMETER XmlObject
    XML-RPC格式的xml
    .INPUTS
    string or xml
    .OUTPUTS
    int
    bool
    double
    datetime
    byte[]
    hashtable
    .EXAMPLE
    ConvertFrom-XmlRpcType -XmlObject "<string>South Dakota</string>"
    #>

    [CmdletBinding()]
    [OutputType([int])]
    [OutputType([bool])]
    [OutputType([double])]
    [OutputType([datetime])]
    [OutputType([byte[]])]
    [OutputType([hashtable])]
    param (
        [Parameter(Mandatory=$true)]
        $XmlObject
    )

    if ($XmlObject -is [string]) {
        $XmlObject=$([xml]$XmlObject).DocumentElement
    } elseif ($XmlObject -is [xml]) {
        $XmlObject=$XmlObject.DocumentElement
    }

    if ($XmlObject -isnot [System.Xml.XmlElement]) {
        throw "Only types [string](xml format), [xml], [System.Xml.XmlElement] are supported"
    }

    switch ($XmlObject.Name) {
        # four-byte signed integer
        "i4" {
            [int]::Parse($XmlObject.InnerText)
            break
        }
        "int" {
            [int]::Parse($XmlObject.InnerText)
            break
        }
        # 0 (false) or 1 (true)
        "boolean" {
            if ($XmlObject.InnerText -eq "1") {return $true} else {return $false}
            break
        }
        # string
        "string" {
            $XmlObject.InnerText
            break
        }
        # double-precision signed floating point number
        "double" {
            [double]::Parse($XmlObject.InnerText)
            break
        }
        # date/time
        "dateTime.iso8601" {
            [datetime]::ParseExact($XmlObject.InnerText, "yyyyMMddTHH:mm:ss", [cultureinfo]::InvariantCulture)
            break
        }
        # base64-encoded binary
        "base64" {
            [System.Convert]::FromBase64String($XmlObject.InnerText)
            break
        }
        # <struct>s
        "struct" {
            $hashTable=@{}
            $XmlObject.SelectNodes("member") | ForEach-Object {
                $hashTable[$_.name] = $(ConvertFrom-XmlRpcType -XmlObject $_.value.FirstChild)
            }
            return $hashTable
            break
        }
        # <array>s
        "array" {
            $XmlObject.SelectNodes("data/value") | ForEach-Object {
                ConvertFrom-XmlRpcType -XmlObject $_.FirstChild
            }
            break
        }
    }
}