# encoding: ascii
# api: powershell
# title: SoapAction Wsdl
# description: Obtain the correct SoapAction string when a webservice method is passed (read from a Soap request).					
# version: 0.1
# type: function
# author: Daniel Cheng
# license: CC0
# function: Find-SoapAction
# x-poshcode-id: 5157
# x-archived: 2014-05-15T01:30:16
# x-published: 2014-05-09T20:45:00
#
# e.g. Find-SoapAction -method GetUserProfile -wsdl http://server/AuthenticationService.svc?wsdl
#
function Find-SoapAction
{
    [CmdletBinding(PositionalBinding=$false)]
    param(
        [string]$method,
        [uri]$wsdl # supports files
    )
    
 
    Add-Type -AssemblyName "System.Web.Services"
    $webClient = New-Object -TypeName System.Net.WebClient
    $serviceDescriptionCollection = New-Object -TypeName System.Web.Services.Description.serviceDescriptionCollection

    function private:Get-ServiceDescription([uri]$WsdlLocation)
    {
        $error.Clear()
        Write-Verbose "Trying $($WsdlLocation).."
        # accept all certs in retrieving Wsdl if https is used
        if ($WsdlLocation.Scheme -eq "https"){[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}}
        try {
            $wsdlStream = $WebClient.OpenRead($WsdlLocation)
            $serviceDescription = [Web.Services.Description.ServiceDescription]::Read($wsdlStream)
            $wsdlStream.Close()
        } catch {
            Write-Verbose "$($WsdlLocation) retrieval failed!"
            return $false
        }
        Write-Verbose "$($WsdlLocation) retrieval success!"
        return $serviceDescription
    }

    function private:Get-BindingOperation
    {
        param($serviceDescription)
        foreach ($message in $serviceDescription.Messages)
        {
            Write-Verbose "comparing $($message.parts.element.name) with $($method)"
             # http://msdn.microsoft.com/en-us/library/system.xml.xmlqualifiedname_properties(v=vs.110).aspx
            if ($message.parts.element.name -eq $method)
            {
                foreach ($PortTypeOperation in $serviceDescription.PortTypes.Operations.Messages) {
                    Write-Verbose "comparing $($PortTypeOperation.Message.Name) with $($message.name)"
                    if ($PortTypeOperation.Message.Name -eq $message.Name)
                    {
                        Write-Verbose $PortTypeOperation.operation.name
                        return $PortTypeOperation.operation.name
                    }
                }
            }
        }
    }

    function private:Find-BindingOperationMapping
    {
        param($bindingOperation, $serviceDescription)
        foreach ($servicePortBinding in $serviceDescription.services.ports.Binding)
        {
            foreach ($Binding in $serviceDescription.Bindings)
            {
                #quick hack to get rid of soap12 services
                Write-Verbose "comparing $($servicePortBinding.Name) with $($Binding.Name)"
                if (($servicePortBinding.Name -eq $Binding.Name) -and ($servicePortBinding.name -notlike "*soap12*"))
                {
                    foreach ($Operation in $Binding.Operations)
                    {
                        if ($Operation.Name -eq $bindingOperation)
                        {
                            return ([uri]$Operation.Extensions.soapAction).AbsoluteUri
                        }
                    }
                }
    
            }
        }
    }

    if ($wsdl.IsFile)
    {
        $location = Get-ServiceDescription -WsdlLocation $wsdl
    } else {
        try {
            $location = Get-ServiceDescription -WsdlLocation ($wsdl.AbsoluteUri -replace "\?wsdl", "?singleWsdl")
            if (![bool]$location) {throw "error"}
        } catch {
            $location = Get-ServiceDescription -WsdlLocation $wsdl
        }
    }    
    
    # build serviceDescriptionCollection object (from all imported Wsdls if applicable)
    if ($location.GetType().BaseType -eq [System.Web.Services.Description.NamedItem])
    {
        try {
            [void]$serviceDescriptionCollection.add($location)
        } catch {
            Write-Verbose "[serviceDescriptionCollection] $($error)"
        } finally {
            if ([bool]$location.imports.count)
            {
                foreach ($importLocation in $location.Imports.location)
                {
                    Write-Verbose "Importing from $($importLocation).."
                    try {
                        [void]$serviceDescriptionCollection.add((Get-ServiceDescription $importLocation))
                    } catch {
                        Write-Verbose "[importLocation] $($error)"
                    }
                }
            }
        }
    }

    # finally find the action
    foreach ($serviceDescription in $serviceDescriptionCollection)
    {
        $bindingOperation = Get-BindingOperation -serviceDescription $serviceDescription
        if ($bindingOperation)
        {
            foreach ($serviceDescription in $serviceDescriptionCollection)
            {
                Find-BindingOperationMapping -bindingOperation $bindingOperation -serviceDescription $serviceDescription
            }
            
        }
    }
}
