Client.psm1

#Requires -version 3.0
Set-StrictMode -Version Latest
$script:channelFactory = $null;

<#
**************************************************
* Private members
**************************************************
#>


Function _GetCoreServiceBinding
{
    $settings = Get-TridionCoreServiceSettings

    $quotas = New-Object System.Xml.XmlDictionaryReaderQuotas;
    $quotas.MaxStringContentLength = [int]::MaxValue;
    $quotas.MaxArrayLength = [int]::MaxValue;
    $quotas.MaxBytesPerRead = [int]::MaxValue;

    switch($settings.ConnectionType)
    {
        "LDAP" 
        { 
            $binding = New-Object System.ServiceModel.WSHttpBinding;
            $binding.Security.Mode = [System.ServiceModel.SecurityMode]::Message;
            $binding.Security.Transport.ClientCredentialType = (_GetClientCredentialType -DefaultValue "Basic");
        }
        "LDAP-SSL"
        {
            $binding = New-Object System.ServiceModel.WSHttpBinding;
            $binding.Security.Mode = [System.ServiceModel.SecurityMode]::Transport;
            $binding.Security.Transport.ClientCredentialType = (_GetClientCredentialType -DefaultValue "Basic")
        }
        "netTcp" 
        { 
            $binding = New-Object System.ServiceModel.NetTcpBinding; 
            $binding.transactionFlow = $true;
            $binding.transactionProtocol = [ServiceModel.TransactionProtocol]::OleTransactions;
            $binding.Security.Mode = [System.ServiceModel.SecurityMode]::Transport;
            $binding.Security.Transport.ClientCredentialType = "Windows";
        }
        "SSL"
        {
            $binding = New-Object System.ServiceModel.WSHttpBinding;
            $binding.Security.Mode = [System.ServiceModel.SecurityMode]::Transport;
            $binding.Security.Transport.ClientCredentialType = (_GetClientCredentialType -DefaultValue "Windows")
        }
        "Basic"
        {
            $binding = New-Object System.ServiceModel.BasicHttpBinding;
            $binding.Security.Mode = [System.ServiceModel.BasicHttpSecurityMode]::TransportCredentialOnly;
            $binding.Security.Transport.ClientCredentialType = (_GetClientCredentialType -DefaultValue "Windows")
        }
        "Basic-SSL"
        {
            $binding = New-Object System.ServiceModel.BasicHttpsBinding;
            $binding.Security.Mode = [System.ServiceModel.BasicHttpsSecurityMode]::Transport;
            $binding.Security.Transport.ClientCredentialType = (_GetClientCredentialType -DefaultValue "Windows")
        }
        "Federation-SSL"
        {
            $binding = New-Object System.ServiceModel.WS2007FederationHttpBinding;
            $binding.Security.Mode = [System.ServiceModel.WSFederationHttpSecurityMode]::TransportWithMessageCredential;
            $binding.Security.Message.IssuerAddress = 'http://some.url'
            $binding.Security.Message.IssuerBinding = New-Object -TypeName System.ServiceModel.BasicHttpBinding
        }
        default 
        { 
            $binding = New-Object System.ServiceModel.WSHttpBinding; 
            $binding.Security.Mode = [System.ServiceModel.SecurityMode]::Message;
            $binding.Security.Transport.ClientCredentialType = (_GetClientCredentialType -DefaultValue "Windows")
        }
    }

    $binding.SendTimeout = $settings.ConnectionSendTimeout;
    $binding.MaxReceivedMessageSize = [int]::MaxValue;
    $binding.ReaderQuotas = $quotas;
    return $binding;
}

Function _NewAssemblyInstance($instanceTypeName, $binding, $endpoint)
{
    return [Activator]::CreateInstance($instanceTypeName, $binding, $endpoint);
}

Function _GetClientCredentialType($DefaultValue)
{
    $settings = Get-TridionCoreServiceSettings;

    if ($settings.CredentialType -eq 'Default' -or !$settings.CredentialType)
    {
        return $DefaultValue;
    }

    return $settings.CredentialType;
}

Function _SetCredential($client, $credential)
{
    $client.ClientCredentials.Windows.ClientCredential = [System.Net.NetworkCredential]$credential;
}

Function _SetImpersonateUser($client, $userName)
{
    $client.Impersonate($userName) | Out-Null;
}

Function _GetChannelFactory($instanceType, $binding, $endpoint)
{
    $factory = New-Object System.ServiceModel.ChannelFactory[$instanceType] -ArgumentList ($binding, $endpoint);
    $factory.Credentials.UseIdentityConfiguration = $true;

    return $factory;
}

Function _GetAdfsToken($serviceInfo)
{
    $binding = New-Object -TypeName System.ServiceModel.WS2007HttpBinding -ArgumentList ([System.ServiceModel.SecurityMode] 'TransportWithMessageCredential');
    $binding.Security.Message.ClientCredentialType = 'UserName';
    $binding.Security.Message.EstablishSecurityContext = $false;

    $endpoint = New-Object -TypeName System.ServiceModel.EndpointAddress -ArgumentList ($serviceInfo.AdfsUrl);

    $factory = New-Object -TypeName System.ServiceModel.Security.WSTrustChannelFactory -ArgumentList ($binding, $endpoint);

    $credential = [System.Net.NetworkCredential]$serviceInfo.Credential;

    if ($credential.Domain)
    {
        $fullUsername = "{0}\{1}" -f $credential.Domain, $credential.Username;
    }
    else
    {
        $fullUsername = $credential.Username;
    }

    $factory.Credentials.UserName.UserName = $fullUsername;
    $factory.Credentials.UserName.Password = $credential.Password;
    $channel = $factory.CreateChannel();

    $request = New-Object -TypeName System.IdentityModel.Protocols.WSTrust.RequestSecurityToken -Property @{
        RequestType = [System.IdentityModel.Protocols.WSTrust.RequestTypes]::Issue
        AppliesTo   = $serviceInfo.EndpointUrl
        TokenType   = 'urn:oasis:names:tc:SAML:2.0:assertion'
    }

    return $channel.Issue($request);
}

<#
**************************************************
* Public members
**************************************************
#>

Function Get-TridionCoreServiceClient
{
    <#
    .Synopsis
    Gets a client capable of accessing the Tridion Core Service.
 
    .Description
    Gets a session-aware Core Service client. The Core Service version, binding, and host machine can be modified using Set-TridionCoreServiceSettings.
 
    .Notes
    Make sure you call Close-TridionCoreServiceClient when you are done with the client (i.e. in a finally block).
 
    .Inputs
    None.
 
    .Outputs
    Returns a client of type [Tridion.ContentManager.CoreService.Client.SessionAwareCoreServiceClient].
 
    .Link
    Get the latest version of this script from the following URL:
    https://github.com/pkjaer/tridion-powershell-modules
 
    .Example
    $client = Get-TridionCoreServiceClient;
    if ($client -ne $null)
    {
        try
        {
            $client.GetCurrentUser();
        }
        finally
        {
            Close-TridionCoreServiceClient $client;
        }
    }
 
    #>

    [CmdletBinding()]
    Param(
        # The name (including domain) of the user to impersonate when accessing Tridion.
        # When omitted the current user will be executing all Tridion commands.
        [Parameter(ValueFromPipelineByPropertyName=$true)]
        [string]$ImpersonateUserName
    )

    Begin
    {
        # Load required .NET assemblies
        Add-Type -AssemblyName System.ServiceModel

        # Load information about the Core Service client available on this system
        $serviceInfo = Get-TridionCoreServiceSettings

        Write-Verbose ("Connecting to the Core Service at {0}..." -f $serviceInfo.HostName);

        # Load the Core Service Client
        $endpoint = New-Object System.ServiceModel.EndpointAddress -ArgumentList $serviceInfo.EndpointUrl
        $binding = _GetCoreServiceBinding;

        #Load the assembly without locking the file
        $assemblyBytes = [IO.File]::ReadAllBytes($serviceInfo.AssemblyPath);
        if (!$assemblyBytes) { throw "Unable to load the assembly at: " + $serviceInfo.AssemblyPath; }
        $assembly = [Reflection.Assembly]::Load($assemblyBytes);
        $instanceType = $assembly.GetType($serviceInfo.ClassName, $true, $true);
    }

    Process
    {
        try
        {
            if ($serviceInfo.ConnectionType -eq 'Federation' -or $serviceInfo.ConnectionType -eq 'Federation-SSL')
            {
                Write-Verbose "Using Federation";
                $script:channelFactory = _GetChannelFactory $instanceType $binding $endpoint;
                $token = _GetAdfsToken $serviceInfo;
                $proxy = $channelFactory.CreateChannelWithIssuedToken($token);
            }
            else
            {
                $proxy = _NewAssemblyInstance $instanceType.FullName $binding $endpoint;

                if ($serviceInfo.Credential)
                {
                    _SetCredential $proxy $serviceInfo.Credential;

                    if ($binding.Security.Transport.ClientCredentialType -eq "Basic")
                    {
                        if ($proxy.ClientCredentials.Windows.ClientCredential.Domain)
                        {
                            $fullUsername = "{0}\{1}" -f $proxy.ClientCredentials.Windows.ClientCredential.Domain, $proxy.ClientCredentials.Windows.ClientCredential.Username
                        }
                        else
                        {
                            $fullUsername = $proxy.ClientCredentials.Windows.ClientCredential.Username
                        }
                        $proxy.ClientCredentials.UserName.UserName = $fullUsername;
                        $proxy.ClientCredentials.UserName.Password = $proxy.ClientCredentials.Windows.ClientCredential.Password;
                    }
                }

                if ($ImpersonateUserName)
                {
                    _SetImpersonateUser $proxy $ImpersonateUserName;
                }
            }

            return $proxy;
        }
        catch [System.Exception]
        {
            Write-Error $_;
            return $null;
        }
    }
}

Function Close-TridionCoreServiceClient
{
    <#
    .Synopsis
    Closes the Core Service connection.
 
    .Description
    This will close the connection, even if it is in a faulted state due to previous exceptions.
 
    .Notes
    You should call this method in your 'finally' clause or 'End' step.
 
    .Inputs
    The Core Service client to close.
 
    .Outputs
    None.
 
    .Link
    Get the latest version of this script from the following URL:
    https://github.com/pkjaer/tridion-powershell-modules
 
    .Example
    $client = Get-TridionCoreServiceClient;
    try
    {
        $client.GetCurrentUser();
    }
    finally
    {
        Close-TridionCoreServiceClient $client;
    }
 
    #>

    [CmdletBinding()]
    Param(
        # The client to close. It is allowed to be null.
        [Parameter(ValueFromPipeline=$true)]
        $client
    )

    Process
    {
        if ($script:channelFactory -ne $null)
        {
            if ($script:channelFactory.State -eq 'Faulted')
            {
                $script:channelFactory.Abort() | Out-Null;
            }
            else
            {
                $script:channelFactory.Close() | Out-Null; 
            }
        } 
        else
        {
            if ($client -ne $null) 
            {
                if ($client.State -eq 'Faulted')
                {
                    $client.Abort() | Out-Null;
                }
                else
                {
                    $client.Close() | Out-Null; 
                }
            }
        }
    }
}

<#
**************************************************
* Export statements
**************************************************
#>

Export-ModuleMember Get-Tridion*, Close-Tridion* -Alias *