PowerShell's New-WebServiceProxy CmdLet with Client Certificates

September 17, 2017 by Christopher Walker

I was doing some work in PowerShell a couple of months ago and found myself needing to use Web Services (SOAP Calls) of an enterprise system set in place. I had planned on using PowerShell’s New-WebServiceProxy CmdLet but found myself running into issues due to the server requiring client certificates for authentication. I had resolved to write the XML programmatically and use the Invoke-WebRequest CmdLet but as the script grew this became problematic for scaling.

I reread the documentation for New-WebServiceProxy and noticed that you could insert client certificates into the calls post proxy creation but not during the creation when calling it (ClientCertificates is not a parameter for the CmdLet). I also noticed you could use local WSDL files as the source when calling the CmdLet. I decided to combine the utility of Invoke-WebRequest and the New-WebServiceProxy to first download the WSDL file, save it to a temporary file (a real temporary file, not just something in C:\temp) and then call the proxy with the contents of the local temporary file and then add in the client certificates post-proxy-creation.

To top it all off, I assembled a proxy command based off of New-WebServiceProxy’s command metadata as the wrapper for the functionality to make the call more succinct syntactically.

Function New-ProxyWebServiceProxy {
    [CmdletBinding(DefaultParameterSetName='NoCredentials', HelpUri='http://go.microsoft.com/fwlink/?LinkID=135238')]
    Param (
        [Parameter(Mandatory=$true, Position=0)]
        [Alias('WL','WSDL','Path')]
        [ValidateNotNullOrEmpty()]
        [uri]
        ${Uri},

        [Parameter(ParameterSetName='Credential')]
        [Alias('Cred')]
        [ValidateNotNullOrEmpty()]
        [pscredential]
        [System.Management.Automation.CredentialAttribute()]
        ${Credential},

        [Alias('Cert')]
        [ValidateNotNullOrEmpty()]
        [System.Security.Cryptography.X509Certificates.X509Certificate2]
        ${Certificate},

        [Parameter(Position=1)]
        [Alias('FileName','FN')]
        [ValidateNotNullOrEmpty()]
        [string]
        ${Class},

        [Parameter(Position=2)]
        [Alias('NS')]
        [ValidateNotNullOrEmpty()]
        [string]
        ${Namespace},

        [Parameter(ParameterSetName='UseDefaultCredential')]
        [Alias('UDC')]
        [ValidateNotNull()]
        [switch]
        ${UseDefaultCredential}
    )
    Begin {
        Try {
            $outBuffer = $null
            If ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer)) {
                $PSBoundParameters['OutBuffer'] = 1
            }

            If ($Certificate) {
                $TempFile = [System.IO.Path]::GetTempFileName() + "_wsdl.xml"

                $WebRequest = @{
                    "Certificate" = $Certificate
                    "URI" = $URI
                }

                If ($Credential) {
                    $WebRequest.Credential = $Credential
                    #$WebRequest.ProxyCredential = $Credential
                } ElseIf ($UseDefaultCredential) {
                    $WebRequest.UseDefaultCredentials = $True
                    #$WebRequest.ProxyUseDefaultCredentials = $True
                }

                $Response = Invoke-WebRequest @WebRequest
                $Content = [XML]$Response.Content
                $FirstChild = $Content.FirstChild

                If ($FirstChild.Name -eq "xml") {
                    $Content.RemoveChild($FirstChild) | Out-Null
                }

                $Content.OuterXml | Out-File -FilePath $TempFile
                $PSBoundParameters.Uri = "file://" + $TempFile
            }

            $PSBoundParameters.Remove("Certificate") | Out-Null

            $wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('New-WebServiceProxy', [System.Management.Automation.CommandTypes]::Cmdlet)
            $scriptCmd = {& $wrappedCmd @PSBoundParameters }
            $steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin)
            $steppablePipeline.Begin($PSCmdlet)
        } Catch {
            Throw $_
        }
    }
    Process {
        Try {
            $Proxy = New-WebServiceProxy @PSBoundParameters
            $Proxy.ClientCertificates.Add($Certificate) | Out-Null
            $Proxy
        } Catch {
            Throw $_
        }
    }
    End {
        Try {
            If ($TempFile) {
                Remove-Item $TempFile
            }
        } Catch {
            Throw
        }
    }
    <#
    .ForwardHelpTargetName New-WebServiceProxy
    .ForwardHelpCategory Cmdlet
    #>
}

Get the GitHub gist for this code here

If you follow along in the code, you’ll see in the Begin block, the download of the WSDL occurs and is stored in a temporary file. The temporary file path then overwrites the URI of the bound parameter. After that, the Certificate parameter is dropped in order to emulate the New-WebServiceProxy CmdLet parameters for splatting.

The Process block handles the call to the original New-WebServiceProxy CmdLet and is assigned to a variable $Proxy. Here, the client certificate originally passed to the proxy command is added to the ClientCertificates of the newly created web service proxy.

The End block removes the temporary file that was created for cleanup purposes.

The call after that is simple once you have your certificate assigned to a variable:

New-ProxyWebServiceProxy -Uri https://your.certificate.authenticated/wsdl-location -Certificate $Certificate

I hope this helps and if you run into any problems, leave a comment.

Privacy Policy

© 2017 | Powered by Hugo and lots of motivation.