Microsoft.Xrm.Data.PowerShell.psm1

### https://msdn.microsoft.com/en-us/library/microsoft.xrm.tooling.connector.crmserviceclient_methods(v=crm.6).aspx ###
# Copyright © Microsoft Corporation. All Rights Reserved.
# This code released under the terms of the
# Microsoft Public License (MS-PL, http://opensource.org/licenses/ms-pl.html.)
# Sample Code is provided for the purpose of illustration only and is not intended to be used in a production environment.
# THIS SAMPLE CODE AND ANY RELATED INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
# INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
# We grant You a nonexclusive, royalty-free right to use and modify the Sample Code and to reproduce and distribute the object code form of the Sample Code, provided that.
# You agree:
# (i) to not use Our name, logo, or trademarks to market Your software product in which the Sample Code is embedded;
# (ii) to include a valid copyright notice on Your software product in which the Sample Code is embedded;
# and (iii) to indemnify, hold harmless, and defend Us and Our suppliers from and against any claims or lawsuits, including attorneys’ fees, that arise or result from the use or distribution of the Sample Code

function Connect-CrmOnlineDiscovery{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml
    [CmdletBinding()]
    PARAM(
        [parameter(Mandatory=$false)]
        [PSCredential]$Credential, 
        [Parameter(Mandatory=$false)]
        [switch]$InteractiveMode
    )
    AddTls12Support #make sure tls12 is enabled
    if($InteractiveMode)
    {
        $global:conn = Get-CrmConnection -InteractiveMode -Verbose
        
        Write-Verbose "You are now connected and may run any of the CRM Commands."
        return $global:conn 
    }
    else
    {
        $onlineType = "Office365"
        if($Credential -eq $null -And !$Interactive)
        {
            $Credential = Get-Credential
        }
        $crmOrganizations = Get-CrmOrganizations -Credential $Credential -OnLineType $onlineType -Verbose 
        $i = 0
          
        if($crmOrganizations.Count -gt 0)
        {    

            if($crmOrganizations.Count -eq 1)
            {
                $orgNumber = 0
            }
            else
            {
                $crmOrganizations = $crmOrganizations | sort-object FriendlyName
                foreach($crmOrganization in $crmOrganizations)
                {   $friendlyName = $crmOrganization.FriendlyName

                    $message = "[$i] $friendlyName (" + $crmOrganization.WebApplicationUrl + ")"
                    Write-Host $message 
                    $i++
                }
                $orgNumber = Read-Host "`nSelect CRM Organization by index number"
    
                Write-Verbose ($crmOrganizations[$orgNumber]).UniqueName
            }
            $global:conn = Get-CrmConnection -Credential $Credential -DeploymentRegion $crmOrganizations[$orgNumber].DiscoveryServerShortname -OnLineType $onlineType -OrganizationName ($crmOrganizations[$orgNumber]).UniqueName -Verbose

            #yes, we know this isn't recommended BUT this cmdlet is only valid for user interaction in the console and shouldn't be used for non-interactive scenarios
            Write-Host "`nYou are now connected to: $(($crmOrganizations[$orgNumber]).UniqueName)" -foregroundcolor yellow
            Write-Host "For a list of commands run: Get-Command -Module Microsoft.Xrm.Data.Powershell" -foregroundcolor yellow
            return $global:conn    
        }
    }
}

function Connect-CrmOnline{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml
    [CmdletBinding()]
    PARAM(
        [parameter(Position=1, Mandatory=$true)]
        [PSCredential]$Credential, 
        [Parameter(Position=2,Mandatory=$true)]
        [ValidatePattern('([\w-]+).crm([0-9]*).(microsoftdynamics|dynamics|crm[\w-]*).(com|de)')]
        [string]$ServerUrl, 
        [Parameter(Position=3,Mandatory=$false)]
        [switch]$ForceDiscovery,
        [Parameter(Position=4,Mandatory=$false)]
        [switch]$ForceOAuth, 
        [Parameter(Position=5,Mandatory=$false)]
��������[ValidateScript({
������������try {
����������������[System.Guid]::Parse($_) | Out-Null
����������������$true
������������} catch {
����������������$false
������������}
��������})]
��������[string]$OAuthClientId,
        [Parameter(Position=6,Mandatory=$false)]
��������[string]$OAuthRedirectUri
    )
    AddTls12Support #make sure tls12 is enabled
    if($ServerUrl.StartsWith("https://","CurrentCultureIgnoreCase") -ne $true){
        Write-Verbose "ServerUrl is missing https, fixing URL: https://$ServerUrl"
        $ServerUrl = "https://" + $ServerUrl
    }
    Write-Verbose "Connecting to: $ServerUrl"
    $cs = "RequireNewInstance=True"
    $cs+= ";Username=$($Credential.UserName)"
    $cs+= ";Password=$($Credential.GetNetworkCredential().Password)"
    $cs+= ";Url=$ServerUrl"
    
    #Default to Office365 Auth, allow oAuth to be used
    if(!$OAuthClientId -and !$ForceOAuth){
        Write-Verbose "Using AuthType=Office365"
        $cs += ";AuthType=Office365"
    }
    else{
        Write-Verbose "Params -> ForceOAuth: {$ForceOAuth} ClientId: {$OAuthClientId} RedirectUri: {$OAuthRedirectUri}"
        #use the clientid if provided, else use a provided clientid
        if($OAuthClientId){
            Write-Verbose "Using provide "
            $cs += ";AuthType=OAuth;ClientId=$OAuthClientId"
            if($OAuthRedirectUri){
                $cs += ";redirecturi=$OAuthRedirectUri"
            }
        }
        else{
            $cs+=";AuthType=OAuth;ClientId=2ad88395-b77d-4561-9441-d0e40824f9bc"
            $cs+=";redirecturi=app://5d3e90d6-aa8e-48a8-8f2c-58b45cc67315"
        }
    }
    #disable the discovery check by default
    if($ForceDiscovery){ 
        Write-Verbose "ForceDiscovery: SkipDiscovery=False"
        $cs+=";SkipDiscovery=False" 
    }
    else{ 
        Write-Verbose "Default: SkipDiscovery=True"
        $cs+=";SkipDiscovery=True" 
    }
    try
    {
        if(!$cs -or $cs.Length -eq 0){
            throw "Cannot create the CrmServiceClient, the connection string is null"
        }
        $global:conn = New-Object Microsoft.Xrm.Tooling.Connector.CrmServiceClient -ArgumentList $cs
        return $global:conn
    }
    catch
    {
        throw $_
    }    
}

function Connect-CrmOnPremDiscovery{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml
    [CmdletBinding()]
    PARAM(
        [parameter(Mandatory=$false, ParameterSetName="ServerUrl")]
        [PSCredential]$Credential, 
        [Parameter(Mandatory=$true, ParameterSetName="ServerUrl")]
        [ValidatePattern('http(s)?://[\w-]+(/[\w- ./?%&=]*)?')]
        [Uri]$ServerUrl,
        [Parameter(Mandatory=$false, ParameterSetName="ServerUrl")]
        [string]$OrganizationName,
        [Parameter(Mandatory=$false, ParameterSetName="ServerUrl")]
        [string]$HomeRealmUrl,
        [Parameter(Mandatory=$false, ParameterSetName="InteractiveMode")]
        [switch]$InteractiveMode
    )
    AddTls12Support #make sure tls12 is enabled
    if($InteractiveMode)
    {
        $global:conn = Get-CrmConnection -InteractiveMode -Verbose
        Write-Verbose "You are now connected and may run any of the CRM Commands."
        return $global:conn 
    }
    else
    {
        if($Credential -eq $null -And !$Interactive)
        {
            $Credential = Get-Credential
        }

        # If Organization Name is pased, use it, otherwise retrieve all organizations the user belongs to.
        if($OrganizationName -ne '')
        {
            $organizationName = $OrganizationName
        }
        else
        {
            $crmOrganizations = Get-CrmOrganizations -Credential $Credential -ServerUrl $ServerUrl -Verbose 
        
            if($crmOrganizations.Count -gt 0)
            {    
                if($crmOrganizations.Count -eq 1)
                {
                    $orgNumber = 0
                }
                else
                {
                    $i = 0
                    $crmOrganizations = $crmOrganizations | sort-object FriendlyName
                    foreach($crmOrganization in $crmOrganizations)
                    {   
                        $friendlyName = $crmOrganization.FriendlyName
                        $message = "[$i] $friendlyName (" + $crmOrganization.WebApplicationUrl + ")"
                        Write-Host $message 
                        $i++
                    }
                    $orgNumber = Read-Host "`nSelect CRM Organization by index number"                                    
                }            
                
                # Store the OrganizationName
                Write-Verbose ($crmOrganizations[$orgNumber]).UniqueName    
                $organizationName = ($crmOrganizations[$orgNumber]).UniqueName
            }
            else
            {
                Write-Warning "User belongs to no organization."
                return
            }
        }          

        if($HomeRealmUrl -eq '')
        {
            $global:conn = Get-CrmConnection -Credential $Credential -ServerUrl $ServerUrl -OrganizationName $organizationName -Verbose
        }
        else
        {
            $global:conn = Get-CrmConnection -Credential $Credential -ServerUrl $ServerUrl -OrganizationName $organizationName -HomeRealmUrl $HomeRealmUrl -Verbose
        }
        #yes, we know this isn't recommended BUT this cmdlet is only valid for user interaction in the console and shouldn't be used for non-interactive scenarios
        Write-Host "`nYou are now connected to: $organizationName" -foregroundcolor yellow
        Write-Host "For a list of commands run: Get-Command -Module Microsoft.Xrm.Data.Powershell" -foregroundcolor yellow
        return $global:conn    
    }
}

function New-CrmRecord{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml
    [CmdletBinding()]
    PARAM(
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn,
        [parameter(Mandatory=$true, Position=1, ParameterSetName="NameAndFields")]
        [string]$EntityLogicalName,
        [parameter(Mandatory=$true, Position=2, ParameterSetName="NameAndFields")]
        [hashtable]$Fields,
        [parameter(Mandatory=$true, Position=1, ParameterSetName="CrmRecord")]
        [PSObject]$CrmRecord,
    [parameter(Mandatory=$false, Position=2, ParameterSetName="CrmRecord")]
        [switch]$PreserveCrmRecordId
    )

    $conn = VerifyCrmConnectionParam $conn

    $newfields = New-Object 'System.Collections.Generic.Dictionary[[String], [Microsoft.Xrm.Tooling.Connector.CrmDataTypeWrapper]]'
    
    if($CrmRecord -ne $null)
    {
        $EntityLogicalName = $CrmRecord.ReturnProperty_EntityName
        $atts = Get-CrmEntityAttributes -conn $conn -EntityLogicalName $EntityLogicalName
        foreach($crmFieldKey in ($CrmRecord | Get-Member -MemberType NoteProperty).Name)
        {
            if($crmFieldKey.EndsWith("_Property"))
            {
                if($CrmRecord.ReturnProperty_Id -eq $CrmRecord.$crmFieldKey.Value -and !$PreserveCrmRecordId)
                {
                    continue;
                }               
                elseif(($atts | ? logicalname -eq $CrmRecord.$crmFieldKey.Key).IsValidForCreate)
                {
                    # Some fields cannot be created even though it is set as IsValidForCreate
                    if($CrmRecord.$crmFieldKey.Key.Contains("addressid"))
                    {
                        continue;
                    }
                    else
                    {
                        $newfield = New-Object -TypeName 'Microsoft.Xrm.Tooling.Connector.CrmDataTypeWrapper'
            
                        $newfield.Type = MapFieldTypeByFieldValue -Value $CrmRecord.$crmFieldKey.Value                 
                        $newfield.Value = $CrmRecord.$crmFieldKey.Value
                        $newfields.Add($CrmRecord.$crmFieldKey.Key, $newfield)
                    }
                }
            }
        }  
    }
    else
    {
        foreach($field in $Fields.GetEnumerator())
        {  
            $newfield = New-Object -TypeName 'Microsoft.Xrm.Tooling.Connector.CrmDataTypeWrapper'
            
            $newfield.Type = MapFieldTypeByFieldValue -Value $field.Value
            
            $newfield.Value = $field.Value
            $newfields.Add($field.Key, $newfield)
        }
    }
    try
    {        
        $result = $conn.CreateNewRecord($EntityLogicalName, $newfields, $null, $false, [Guid]::Empty)
        if(!$result -or $result -eq [System.Guid]::Empty)
        {
            throw LastCrmConnectorException($conn)
        }
    }
    catch
    {
        throw LastCrmConnectorException($conn)
    }

    return $result
}

function Get-CrmRecord{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml
    [CmdletBinding()]
    PARAM(
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn,
        [parameter(Mandatory=$true, Position=1)]
        [string]$EntityLogicalName,
        [parameter(Mandatory=$true, Position=2)]
        [guid]$Id,
        [parameter(Mandatory=$true, Position=3)]
        [string[]]$Fields,
        [parameter(Mandatory=$false, Position=4)]
        [switch]$IncludeNullValue
    )

    $conn = VerifyCrmConnectionParam $conn

    if($Fields -eq "*")
    {
        [Collections.Generic.List[String]]$x = $null
    }
    else
    {
        [Collections.Generic.List[String]]$x = $Fields
    }

    try
    {
        $record = $conn.GetEntityDataById($EntityLogicalName, $Id, $x, [Guid]::Empty)
    }
    catch
    {
        throw LastCrmConnectorException($conn)        
    }    
    
    if($record -eq $null)
    {        
        throw LastCrmConnectorException($conn)
    }
        
    $psobj = New-Object -TypeName System.Management.Automation.PSObject
    $meta = Get-CrmEntityMetadata -conn $conn -EntityLogicalName $EntityLogicalName -EntityFilters Attributes

    if($IncludeNullValue)
    {
        if($Fields -eq "*")
        {
            # Add all fields first
            foreach($attName in $meta.Attributes | ? IsValidForRead -eq $true | select LogicalName | sort LogicalName)
            {
                Add-Member -InputObject $psobj -MemberType NoteProperty -Name $attName.LogicalName -Value $null
                Add-Member -InputObject $psobj -MemberType NoteProperty -Name ($attName.LogicalName + "_Property") -Value $null
            }
        }
        else
        {
            foreach($attName in $Fields)
            {
                Add-Member -InputObject $psobj -MemberType NoteProperty -Name $attName -Value $null
                Add-Member -InputObject $psobj -MemberType NoteProperty -Name ($attName + "_Property") -Value $null
            }
        }
    }
        
    foreach($att in $record.GetEnumerator())
    {       
        $keyName = $att.Key
        
        if(!($psobj | gm).Name.Contains($keyName))
        {
            Add-Member -InputObject $psobj -MemberType NoteProperty -Name $keyName -Value $null
        }

        if($att.Value -is [Microsoft.Xrm.Sdk.EntityReference])
        {
            $psobj.($keyName) = $att.Value.Name
        }
        elseif($att.Value -is [Microsoft.Xrm.Sdk.AliasedValue])
        {
            $psobj.($keyName) = $att.Value.Value
        }
        else
        {
            $psobj.($keyName) = $att.Value
        }                
    }   

    Add-Member -InputObject $psobj -MemberType NoteProperty -Name "original" -Value $record
    Add-Member -InputObject $psobj -MemberType NoteProperty -Name "logicalname" -Value $EntityLogicalName
    Add-Member -InputObject $psobj -MemberType NoteProperty -Name "EntityReference" -Value (New-CrmEntityReference -EntityLogicalName $EntityLogicalName -Id $Id)
    # Add same additional fields to match Get-CrmRecords functions
    Add-Member -InputObject $psobj -MemberType NoteProperty -Name "ReturnProperty_EntityName" -Value $EntityLogicalName
    Add-Member -InputObject $psobj -MemberType NoteProperty -Name "ReturnProperty_Id" -Value $record.($meta.PrimaryIdAttribute)
    
    return $psobj
}

#Alias for Set-CrmRecord
New-Alias -Name Update-CrmRecord -Value Set-CrmRecord

#UpdateEntity
function Set-CrmRecord{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml

    [CmdletBinding()]
    PARAM(
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn,
        [parameter(Mandatory=$true, Position=1, ParameterSetName="CrmRecord")]
        [PSObject]$CrmRecord,
        [parameter(Mandatory=$true, Position=1, ParameterSetName="Fields")]
        [string]$EntityLogicalName,
        [parameter(Mandatory=$true, Position=2, ParameterSetName="Fields")]
        [guid]$Id,
        [parameter(Mandatory=$true, Position=3, ParameterSetName="Fields")]
        [hashtable]$Fields,
        [parameter(Mandatory=$false)]
        [switch]$Upsert,
        [parameter(Mandatory=$false)]
        [AllowNull()]
        [AllowEmptyString()]
        [string]$PrimaryKeyField
    )

    $conn = VerifyCrmConnectionParam $conn
    
    if($CrmRecord -ne $null)
    { 
        $entityLogicalName = $CrmRecord.logicalname
    }
    else
    {
        $entityLogicalName = $EntityLogicalName
    }
    
    # 'PrimaryKeyField' is an options parameter and is used for custom activity entities
    if(-not [string]::IsNullOrEmpty($PrimaryKeyField)) 
    {
         $primaryKeyField = $PrimaryKeyField
    }
    else
    {
        $primaryKeyField = GuessPrimaryKeyField -EntityLogicalName $entityLogicalName
    }

    # If upsert specified
    if($Upsert)
    {
        $retrieveFields = New-Object System.Collections.Generic.List[string]
        if($CrmRecord -ne $null)
        {
            # when CrmRecord passed, assume this comes from other system.
            $id = $CrmRecord.$primaryKeyField
            foreach($crmFieldKey in ($CrmRecord | Get-Member -MemberType NoteProperty).Name)
            {
                if($crmFieldKey.EndsWith("_Property"))
                {
                    $retrieveFields.Add(($CrmRecord.$crmFieldKey).Key)
                }
                elseif(($crmFieldKey -eq "original") -or ($crmFieldKey -eq "logicalname") -or ($crmFieldKey -eq "EntityReference")`
                  -or ($crmFieldKey -like "ReturnProperty_*"))
                {
                    continue
                }
                else
                {
                    # to have original value, rather than formatted value, replace the value from original record.
                    $CrmRecord.$crmFieldKey = $CrmRecord.original[$crmFieldKey+"_Property"].Value
                }
            }            
        }
        else
        {
            foreach($crmFieldKey in $Fields.Keys)
            {
                $retrieveFields.Add($crmFieldKey)
            }           
        }

        $existingRecord = Get-CrmRecord -conn $conn -EntityLogicalName $entityLogicalName -Id $id -Fields $retrieveFields.ToArray() -ErrorAction SilentlyContinue

        if($existingRecord.original -eq $null)
        {
            if($CrmRecord -ne $null)
            {
                $Fields = @{}
                foreach($crmFieldKey in ($CrmRecord | Get-Member -MemberType NoteProperty).Name)
                {
                    if($crmFieldKey.EndsWith("_Property"))
                    {
                        $Fields.Add(($CrmRecord.$crmFieldKey).Key, ($CrmRecord.$crmFieldKey).Value)
                    }
                } 
            }

            if($Fields[$primaryKeyField] -eq $null)
            {
                $Fields.Add($primaryKeyField, $Id)
            }
            # if no record exists, then create new
            $result = New-CrmRecord -conn $conn -EntityLogicalName $entityLogicalName -Fields $Fields

            return $result
        }
        else
        {   
            if($CrmRecord -ne $null)
            {
                # if record exists, then swap original record so that we can compare updated fields
                $CrmRecord.original = $existingRecord.original
            }
        }
    }

    $newfields = New-Object 'System.Collections.Generic.Dictionary[[String], [Microsoft.Xrm.Tooling.Connector.CrmDataTypeWrapper]]'
    
    if($CrmRecord -ne $null)
    {                
        $originalRecord = $CrmRecord.original        
        $Id = $originalRecord[$primaryKeyField]
        
        foreach($crmFieldKey in ($CrmRecord | Get-Member -MemberType NoteProperty).Name)
        {
            $crmFieldValue = $CrmRecord.($crmFieldKey)
            if(($crmFieldKey -eq "original") -or ($crmFieldKey -eq "logicalname") -or ($crmFieldKey -eq "EntityReference")`
              -or ($crmFieldKey -like "*_Property") -or ($crmFieldKey -like "ReturnProperty_*"))
            {
                continue
            }
            elseif($originalRecord[$crmFieldKey+"_Property"].Value -is [bool])
            {
                if($crmFieldValue -is [Int32])
                {
                    if(($originalRecord[$crmFieldKey+"_Property"].Value -and $crmFieldValue -eq 1) -or `
                    (!$originalRecord[$crmFieldKey+"_Property"].Value -and $crmFieldValue -eq 0))
                    {
                        continue 
                    }  
                }
                elseif($crmFieldValue -is [bool])
                {
                    if($crmFieldValue -eq $originalRecord[$crmFieldKey+"_Property"].Value)
                    {
                        continue
                    }
                }
                elseif($crmFieldValue -eq $originalRecord[$crmFieldKey])
                {
                    continue
                }                             
            }
            elseif($originalRecord[$crmFieldKey+"_Property"].Value -is [Microsoft.Xrm.Sdk.OptionSetValue])
            { 
                if($crmFieldValue -is [Microsoft.Xrm.Sdk.OptionSetValue])
                {
                    if($crmFieldValue.Value -eq $originalRecord[$crmFieldKey+"_Property"].Value.Value)
                    {
                        continue
                    }
                } 
                elseif($crmFieldValue -is [Int32])
                {
                    if($crmFieldValue -eq $originalRecord[$crmFieldKey+"_Property"].Value.Value)
                    {
                        continue
                    }
                }
                elseif($crmFieldValue -eq $originalRecord[$crmFieldKey])
                {
                    continue
                }
            }            
            elseif($originalRecord[$crmFieldKey+"_Property"].Value -is [Microsoft.Xrm.Sdk.Money])
            { 
                if($crmFieldValue -is [Microsoft.Xrm.Sdk.Money])
                {
                    if($crmFieldValue.Value -eq $originalRecord[$crmFieldKey+"_Property"].Value.Value)
                    {
                        continue
                    }
                }
                elseif($crmFieldValue -is [decimal] -or $crmFieldValue -is [Int32])
                {
                    if($crmFieldValue -eq $originalRecord[$crmFieldKey+"_Property"].Value.Value)
                    {
                        continue
                    }
                }
                elseif($crmFieldValue -eq $originalRecord[$crmFieldKey])
                {
                    continue
                }
            }
            elseif($originalRecord[$crmFieldKey+"_Property"].Value -is [Microsoft.Xrm.Sdk.EntityReference])
            { 
                if(($crmFieldValue -is [Microsoft.Xrm.Sdk.EntityReference]) -and ($crmFieldValue.Name -eq $originalRecord[$crmFieldKey].Name))
                {
                    continue
                }
                elseif($crmFieldValue -eq $originalRecord[$crmFieldKey])
                {
                    continue
                }
            }
            elseif($crmFieldValue -eq $originalRecord[$crmFieldKey])
            { 
                continue 
            }

            $newfield = New-Object -TypeName 'Microsoft.Xrm.Tooling.Connector.CrmDataTypeWrapper'
            $value = New-Object psobject
            
            # When value set to null, then just use raw type and set value to $null
            if($crmFieldValue -eq $null)
            {
                $newfield.Type = [Microsoft.Xrm.Tooling.Connector.CrmFieldType]::Raw
                $value = $null
            }
            else
            {
                if($CrmRecord.($crmFieldKey + "_Property") -ne $null)
                {
                    $type = $CrmRecord.($crmFieldKey + "_Property").Value.GetType().Name
                }
                else
                {
                    $type = $crmFieldValue.GetType().Name
                }
                switch($type)
                {
                    "Boolean" {
                        $newfield.Type = [Microsoft.Xrm.Tooling.Connector.CrmFieldType]::CrmBoolean
                        if($crmFieldValue -is [Boolean])
                        {
                            $value = $crmFieldValue
                        }
                        else
                        {
                            $value = [Int32]::Parse($crmFieldValue)
                        }
                        break
                    }
                    "DateTime" {
                        $newfield.Type = [Microsoft.Xrm.Tooling.Connector.CrmFieldType]::CrmDateTime
                        if($crmFieldValue -is [DateTime])
                        {
                            $value = $crmFieldValue
                        }
                        else
                        {
                            $value = [DateTime]::Parse($crmFieldValue)
                        }
                        break
                    }
                    "Decimal" {
                        $newfield.Type = [Microsoft.Xrm.Tooling.Connector.CrmFieldType]::CrmDecimal
                        if($crmFieldValue -is [Decimal])
                        {
                            $value = $crmFieldValue
                        }
                        else
                        {
                            $value = [Decimal]::Parse($crmFieldValue)
                        }
                        break
                    }
                    "Single" {
                        $newfield.Type = [Microsoft.Xrm.Tooling.Connector.CrmFieldType]::CrmFloat
                        if($crmFieldValue -is [Single])
                        {
                            $value = $crmFieldValue
                        }
                        else
                        {
                            $value = [Single]::Parse($crmFieldValue)
                        }
                        break
                    }
                    "Money" {
                        $newfield.Type = [Microsoft.Xrm.Tooling.Connector.CrmFieldType]::Raw
                        if($crmFieldValue -is [Microsoft.Xrm.Sdk.Money])
                        {                
                            $value = $crmFieldValue
                        }
                        else
                        {                
                            $value = New-Object -TypeName 'Microsoft.Xrm.Sdk.Money'
                            $value.Value = $crmFieldValue
                        }
                        break
                    }
                    "Int32" {
                        $newfield.Type = [Microsoft.Xrm.Tooling.Connector.CrmFieldType]::CrmNumber
                        if($crmFieldValue -is [Int32])
                        {
                            $value = $crmFieldValue
                        }
                        else
                        {
                            $value = [Int32]::Parse($crmFieldValue)
                        }
                        break
                    }
                    "EntityReference" {
                        $newfield.Type = [Microsoft.Xrm.Tooling.Connector.CrmFieldType]::Raw
                        $value = $crmFieldValue
                        break
                    }
                    "OptionSetValue" {
                        $newfield.Type = [Microsoft.Xrm.Tooling.Connector.CrmFieldType]::Raw
                        if($crmFieldValue -is [Microsoft.Xrm.Sdk.OptionSetValue])
                        {
                            $value = $crmFieldValue                        
                        }
                        else
                        {
                            $value = New-Object -TypeName 'Microsoft.Xrm.Sdk.OptionSetValue'
                            $value.Value = [Int32]::Parse($crmFieldValue)
                        }
                        break
                    }
                    "String" {
                        $newfield.Type = [Microsoft.Xrm.Tooling.Connector.CrmFieldType]::String
                        $value = $crmFieldValue
                        break
                    }
                    default {
                        $newfield.Type = [Microsoft.Xrm.Tooling.Connector.CrmFieldType]::Raw
                        $value = $crmFieldValue
                        break
                    }
                }
            }
            $newfield.Value = $value
            $newfields.Add($crmFieldKey, $newfield)
        }
    }
    else
    {
        foreach($field in $Fields.GetEnumerator())
        {  
            $newfield = New-Object -TypeName 'Microsoft.Xrm.Tooling.Connector.CrmDataTypeWrapper'
            if($field.value -eq $null)
            {
                $newfield.Type = [Microsoft.Xrm.Tooling.Connector.CrmFieldType]::Raw
            }
            else
            {
                $newfield.Type = MapFieldTypeByFieldValue -Value $field.Value
            }
            $newfield.Value = $field.Value
            $newfields.Add($field.Key, $newfield)
        }
    }
    try
    {
        # if no field has new value, then do nothing.
        if($newfields.Count -eq 0)
        {
            return
        }
        $result = $conn.UpdateEntity($entityLogicalName, $primaryKeyField, $Id, $newfields, $null, $false, [Guid]::Empty)
        if(!$result)
        {
            throw LastCrmConnectorException($conn)
        }
    }
    catch
    {
        #TODO: Throw Exceptions back to user
        throw LastCrmConnectorException($conn)
    }
}

#DeleteEntity
function Remove-CrmRecord{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml
    [CmdletBinding()]
    PARAM(
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn,
        [parameter(Mandatory=$true, Position=1, ParameterSetName="CrmRecord", ValueFromPipeline=$True)]
        [PSObject]$CrmRecord,
        [parameter(Mandatory=$true, Position=1, ParameterSetName="Fields")]
        [string]$EntityLogicalName,
        [parameter(Mandatory=$true, Position=2, ParameterSetName="Fields")]
        [guid]$Id
    )

    begin
    {
        $conn = VerifyCrmConnectionParam $conn
    }
    process
    {
        if($CrmRecord -ne $null)
        {
            $EntityLogicalName = $CrmRecord.logicalname
            $Id = $CrmRecord.($EntityLogicalName + "id")
        }

        try
        {
            $result = $conn.DeleteEntity($EntityLogicalName, $Id, [Guid]::Empty)
            if(!$result)
            {
                throw LastCrmConnectorException($conn)
            }
        }
        catch
        {
            throw LastCrmConnectorException($conn)
        }
    }
}

#AddEntityToQueue
function Move-CrmRecordToQueue{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml
    [CmdletBinding()]
    PARAM(
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn,
        [parameter(Mandatory=$true, Position=1, ParameterSetName="CrmRecord")]
        [PSObject]$CrmRecord,
        [parameter(Mandatory=$true, Position=1, ParameterSetName="NameWithId")]
        [string]$EntityLogicalName,
        [parameter(Mandatory=$true, Position=2, ParameterSetName="NameWithId")]
        [guid]$Id,
        [parameter(Mandatory=$true, Position=3)]
        [string]$QueueName,
        [parameter(Mandatory=$true, Position=4)]
        [guid]$WorkingUserId,
        [parameter(Mandatory=$false, Position=5)]
        [bool]$SetWorkingByUser
    )
    $conn = VerifyCrmConnectionParam $conn  
    if($CrmRecord -ne $null)
    {
        $EntityLogicalName = $CrmRecord.logicalname
        $Id = $CrmRecord.($EntityLogicalName + "id")
    }

    try
    {
        $result = $conn.AddEntityToQueue($Id, $EntityLogicalName, $QueueName, $WorkingUserId, $SetWorkingByUser, [Guid]::Empty)
        if(!$result)
        {
            throw LastCrmConnectorException($conn)
        }
    }
    catch
    {
        throw LastCrmConnectorException($conn)
    }
}

#AssignEntityToUser
function Set-CrmRecordOwner{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml
    [CmdletBinding()]
    PARAM(
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn,        
        [parameter(Mandatory=$true, Position=1, ParameterSetName="CrmRecord", ValueFromPipeline=$true)]
        [PSObject]$CrmRecord,
        [parameter(Mandatory=$true, Position=1, ParameterSetName="NameWithId")]
        [string]$EntityLogicalName,
        [parameter(Mandatory=$true, Position=2, ParameterSetName="NameWithId")]
        [guid]$Id,
        [parameter(Mandatory=$true, Position=3)][alias("UserId")]
        [guid]$PrincipalId,
        [parameter(Mandatory=$false, Position=4)]
        [switch]$AssignToTeam
    )
    begin
    {
        $conn = VerifyCrmConnectionParam $conn
    }     
    process
    {
        if($CrmRecord -ne $null)
        {
            $EntityLogicalName = $CrmRecord.logicalname
            $Id = $CrmRecord.($EntityLogicalName + "id")
        }
        try
        {            
            # As CrmClientService does not have method to assign to team, use Organization Request
            if($AssignToTeam){
                write-verbose "Assigning record with Id: $Id to Team with Id: $PrincipalId"
                
                $req = New-Object Microsoft.Crm.Sdk.Messages.AssignRequest
                $req.target = New-CrmEntityReference -EntityLogicalName $EntityLogicalName -Id $Id
                $req.Assignee = New-CrmEntityReference -EntityLogicalName "team" -Id $PrincipalId
                $result = [Microsoft.Crm.Sdk.Messages.AssignResponse]$conn.ExecuteCrmOrganizationRequest($req, $null)
                # If no result returend, then it had an issue.
                if($result -eq $null)
                {
                    $result = $false
                }
            }
            else{
                $result = $conn.AssignEntityToUser($PrincipalId, $EntityLogicalName, $Id, [Guid]::Empty)
            }            
            if(!$result)
            {
                throw LastCrmConnectorException($conn)
            }

            write-verbose "Completed..."
        }
        catch
        {
            throw LastCrmConnectorException($conn)
        }
    }
}

#CloseActivity
function Set-CrmActivityRecordToCloseState{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml
    [CmdletBinding()]
    PARAM(
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn,
        [parameter(Mandatory=$true, Position=1, ParameterSetName="CrmRecord")]
        [PSObject]$CrmRecord,
        [parameter(Mandatory=$true, Position=1, ParameterSetName="NameWithId")]
        [string]$ActivityEntityType,
        [parameter(Mandatory=$true, Position=2, ParameterSetName="NameWithId")]
        [guid]$ActivityId,
        [parameter(Mandatory=$true, Position=3)]
        [string]$StateCode,
        [parameter(Mandatory=$true, Position=4)]
        [string]$StatusCode
    )
    $conn = VerifyCrmConnectionParam $conn   
    if($CrmRecord -ne $null)
    {
        $ActivityEntityType = $CrmRecord.logicalname
        $ActivityId = $CrmRecord.("activityid")
    }
    try
    {
        $result = $conn.CloseActivity($ActivityEntityType, $ActivityId, $StateCode, $StatusCode, [Guid]::Empty)
        if(!$result)
        {
            throw LastCrmConnectorException($conn)
        }
    }
    catch
    {
        throw LastCrmConnectorException($conn)
    }
}

#CreateAnnotation
function Add-CrmNoteToCrmRecord{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml
    [CmdletBinding()]
    PARAM(
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn,
        [parameter(Mandatory=$true, Position=1, ParameterSetName="CrmRecord")]
        [PSObject]$CrmRecord,
        [parameter(Mandatory=$true, Position=1, ParameterSetName="NameWithId")]
        [string]$EntityLogicalName,
        [parameter(Mandatory=$true, Position=2, ParameterSetName="NameWithId")]
        [guid]$Id,
        [parameter(Mandatory=$true, Position=3)]
        [string]$Subject,
        [parameter(Mandatory=$true, Position=4)]
        [string]$NoteText 
    )
    $conn = VerifyCrmConnectionParam $conn   
    if($CrmRecord -ne $null)
    {
        $EntityLogicalName = $CrmRecord.logicalname
        $Id = $CrmRecord.($EntityLogicalName + "id")
    }
    $newfields = New-Object 'System.Collections.Generic.Dictionary[[String], [Microsoft.Xrm.Tooling.Connector.CrmDataTypeWrapper]]'
    $subjectfield = New-Object -TypeName 'Microsoft.Xrm.Tooling.Connector.CrmDataTypeWrapper'
    $subjectfield.Type = [Microsoft.Xrm.Tooling.Connector.CrmFieldType]::String
    $subjectfield.Value = $Subject
    $noteTextfield = New-Object -TypeName 'Microsoft.Xrm.Tooling.Connector.CrmDataTypeWrapper'
    $noteTextfield.Type = [Microsoft.Xrm.Tooling.Connector.CrmFieldType]::String
    $noteTextfield.Value = $NoteText
    $newfields.Add("subject", $subjectfield)
    $newfields.Add("notetext", $noteTextfield)

    try
    {
        $result = $conn.CreateAnnotation($EntityLogicalName, $Id, $newfields, [Guid]::Empty)
        if($result -eq $null)
        {
            throw LastCrmConnectorException($conn)
        }
    }
    catch
    {
        throw LastCrmConnectorException($conn)
    }
}

#CreateEntityAssociation
function Add-CrmRecordAssociation{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml
    [CmdletBinding()]
    PARAM(
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn,
        [parameter(Mandatory=$true, Position=1, ParameterSetName="CrmRecord")]
        [PSObject]$CrmRecord1,
        [parameter(Mandatory=$true, Position=2, ParameterSetName="CrmRecord")]
        [PSObject]$CrmRecord2,
        [parameter(Mandatory=$true, Position=1, ParameterSetName="NameWithId")]
        [string]$EntityLogicalName1,
        [parameter(Mandatory=$true, Position=2, ParameterSetName="NameWithId")]
        [guid]$Id1,
        [parameter(Mandatory=$true, Position=3, ParameterSetName="NameWithId")]
        [string]$EntityLogicalName2,
        [parameter(Mandatory=$true, Position=4, ParameterSetName="NameWithId")]
        [guid]$Id2,
        [parameter(Mandatory=$true, Position=5)]
        [string]$RelationshipName
    )
    $conn = VerifyCrmConnectionParam $conn
    if($CrmRecord1 -ne $null)
    {
        $EntityLogicalName1 = $CrmRecord1.logicalname
        $Id1 = $CrmRecord1.($EntityLogicalName1 + "id")
    }
    if($CrmRecord2 -ne $null)
    {
        $EntityLogicalName2 = $CrmRecord2.logicalname
        $Id2 = $CrmRecord2.($EntityLogicalName2 + "id")
    }
    try
    {
        $result = $conn.CreateEntityAssociation($EntityLogicalName1, $Id1, $EntityLogicalName2, $Id2, $RelationshipName, [Guid]::Empty)
        if(!$result)
        {
            throw LastCrmConnectorException($conn)
        }
    }
    catch
    {
        throw LastCrmConnectorException($conn)
    }
}

#CreateMultiEntityAssociation
function Add-CrmMultiRecordAssociation{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml
    [CmdletBinding()]
    PARAM(
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn,
        [parameter(Mandatory=$true, Position=1, ParameterSetName="CrmRecord")]
        [PSObject]$CrmRecord1,
        [parameter(Mandatory=$true, Position=2, ParameterSetName="CrmRecord")]
        [PSObject[]]$CrmRecord2s,
        [parameter(Mandatory=$true, Position=1, ParameterSetName="NameWithId")]
        [string]$EntityLogicalName1,
        [parameter(Mandatory=$true, Position=2, ParameterSetName="NameWithId")]
        [guid]$Id1,
        [parameter(Mandatory=$true, Position=3, ParameterSetName="NameWithId")]
        [string]$EntityLogicalName2,
        [parameter(Mandatory=$true, Position=4, ParameterSetName="NameWithId")]
        [guid[]]$Id2s,
        [parameter(Mandatory=$true, Position=5)]
        [string]$RelationshipName,
        [parameter(Mandatory=$false, Position=6)]
        [bool]$IsReflexiveRelationship
    )

    $conn = VerifyCrmConnectionParam $conn  

    if($CrmRecord1 -ne $null)
    {
        $EntityLogicalName1 = $CrmRecord1.logicalname
        $Id1 = $CrmRecord1.($EntityLogicalName1 + "id")
    }

    if($CrmRecord2s -ne $null)
    {
        if($CrmRecord2s.Count -ne 0)
        {
            $EntityLogicalName2 = $CrmRecord2s[0].logicalname
            $Ids = New-Object 'System.Collections.Generic.List[System.Guid]'
            foreach($CrmRecord2 in $CrmRecord2s)
            {
                $Ids.Add($CrmRecord2.($EntityLogicalName2 + "id"))
            }
            $Id2s = $Ids.ToArray()
        }
         else
        {
            Write-Warning 'CrmRecords2 does not include any records.'
            break
        }
    }   

    try
    {
        $result = $conn.CreateMultiEntityAssociation($EntityLogicalName1, $Id1, $EntityLogicalName2, $Id2s, $RelationshipName, [Guid]::Empty, $IsReflexiveRelationship)
        if(!$result)
        {
            throw LastCrmConnectorException($conn)
        }
    }
    catch
    {
        throw LastCrmConnectorException($conn)
    }
}

#CreateNewActivityEntry
function Add-CrmActivityToCrmRecord{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml
    [CmdletBinding()]
    PARAM(
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn,        
        [parameter(Mandatory=$true, Position=1, ParameterSetName="CrmRecord", ValueFromPipeline=$true)]
        [PSObject]$CrmRecord,
        [parameter(Mandatory=$true, Position=1, ParameterSetName="NameWithId")]
        [string]$EntityLogicalName,
        [parameter(Mandatory=$true, Position=2, ParameterSetName="NameWithId")]
        [guid]$Id,
        [parameter(Mandatory=$true, Position=3)]
        [string]$ActivityEntityType,
        [parameter(Mandatory=$true, Position=4)]
        [string]$Subject,
        [parameter(Mandatory=$true, Position=5)]
        [string]$Description,
        [parameter(Mandatory=$true, Position=6)]
        [string]$OnwerUserId,
        [parameter(Mandatory=$false, Position=7)]
        [hashtable]$Fields
    )
    begin
    {
        $conn = VerifyCrmConnectionParam $conn
    }  
    process
    {
        if($CrmRecord -ne $null)
        {
            $EntityLogicalName = $CrmRecord.logicalname
            $Id = $CrmRecord.($EntityLogicalName + "id")
        }
        $newfields = New-Object 'System.Collections.Generic.Dictionary[[String], [Microsoft.Xrm.Tooling.Connector.CrmDataTypeWrapper]]'
        if($Fields -ne $null)
        {
            foreach($field in $Fields.GetEnumerator())
            {  
                $newfield = New-Object -TypeName 'Microsoft.Xrm.Tooling.Connector.CrmDataTypeWrapper'
                $newfield.Type = MapFieldTypeByFieldValue -Value $field.Value
                $newfield.Value = $field.Value
                $newfields.Add($field.Key, $newfield)
            }
        }

        try
        {
            $result = $conn.CreateNewActivityEntry($ActivityEntityType, $EntityLogicalName, $Id,
                $Subject, $Description, $OnwerUserId, $newfields, [Guid]::Empty)
            if($result -eq $null)
            {
                throw LastCrmConnectorException($conn)
            }
        }
        catch
        {
            throw LastCrmConnectorException($conn)
        }

        return $result
    }
}

#DeleteEntityAssociation
function Remove-CrmRecordAssociation{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml
    [CmdletBinding()]
    PARAM(
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn,
        [parameter(Mandatory=$true, Position=1, ParameterSetName="CrmRecord")]
        [PSObject]$CrmRecord1,
        [parameter(Mandatory=$true, Position=2, ParameterSetName="CrmRecord")]
        [PSObject]$CrmRecord2,
        [parameter(Mandatory=$true, Position=1, ParameterSetName="NameWithId")]
        [string]$EntityLogicalName1,
        [parameter(Mandatory=$true, Position=2, ParameterSetName="NameWithId")]
        [guid]$Id1,
        [parameter(Mandatory=$true, Position=3, ParameterSetName="NameWithId")]
        [string]$EntityLogicalName2,
        [parameter(Mandatory=$true, Position=4, ParameterSetName="NameWithId")]
        [guid]$Id2,
        [parameter(Mandatory=$true, Position=5)]
        [string]$RelationshipName
    )
    $conn = VerifyCrmConnectionParam $conn    
    if($CrmRecord1 -ne $null)
    {
        $EntityLogicalName1 = $CrmRecord1.logicalname
        $Id1 = $CrmRecord1.($EntityLogicalName1 + "id")
    }
    if($CrmRecord2 -ne $null)
    {
        $EntityLogicalName2 = $CrmRecord2.logicalname
        $Id2 = $CrmRecord2.($EntityLogicalName2 + "id")
    }
    try
    {
        $result = $conn.DeleteEntityAssociation($EntityLogicalName1, $Id1, $EntityLogicalName2, $Id2, $RelationshipName, [Guid]::Empty)
        if(!$result)
        {
            throw LastCrmConnectorException($conn)
        }
    }
    catch
    {
        throw LastCrmConnectorException($conn)
    }
}

#ExecuteWorkflowOnEntity
function Invoke-CrmRecordWorkflow{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml
    [CmdletBinding()]
    PARAM(
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn,

        [parameter(Mandatory=$true, Position=1, ParameterSetName="CrmRecordWithWorkflowId")]
        [parameter(ParameterSetName="CrmRecordWithWorkflowName")]
        [PSObject]$CrmRecord,

        [parameter(Mandatory=$true, Position=1, ParameterSetName="CrmRecordIdWithWorkflowId")]
        [parameter(ParameterSetName="CrmRecordIdWithWorkflowName")]
        [Alias("Id", "StringId")]
        [string]$EntityId,

        [parameter(Mandatory=$true, Position=2, ParameterSetName="CrmRecordIdWithWorkflowName")]
        [parameter(ParameterSetName="CrmRecordWithWorkflowName")]
        [string]$WorkflowName, 

        [parameter(Mandatory=$true, Position=2, ParameterSetName="CrmRecordIdWithWorkflowId")]
        [parameter(ParameterSetName="CrmRecordWithWorkflowId")]
        [string]$WorkflowId
    )
    $conn = VerifyCrmConnectionParam $conn
    if($CrmRecord -ne $null)
    {        
        $Id = $CrmRecord.($CrmRecord.logicalname + "id")
    }
    elseif($EntityId -ne $null)
    {
        $Id = [guid]::Parse($EntityId)
    }
    try
    {
        $result = $null 
        if($WorkflowName -ne $null){
                $fetch = @"
<fetch version="1.0" output-format="xml-platform" mapping="logical" distinct="false">
  <entity name="workflow">
    <attribute name="workflowid" />
    <attribute name="name" />
    <attribute name="primaryentity" />
    <attribute name="type" />
    <order attribute="name" descending="false" />
    <filter type="and">
      <condition attribute="name" operator="eq" value="$WorkflowName" />
      <condition attribute="statecode" operator="eq" value="1" />
      <condition attribute="type" operator="eq" value="1" />
      <condition attribute="rendererobjecttypecode" operator="null" />
      <condition attribute="category" operator="eq" value="0" />
    </filter>
  </entity>
</fetch>
"@

            $workflowResult = (Get-CrmRecordsByFetch -Fetch $fetch -TopCount 1)
            if($workflowResult.NextPage){
                throw "Duplicate workflow detected, try executing the workflow by its ID"
            }
            $WorkflowId = $workflowResult.CrmRecords[0].workflowid
        }
        if($WorkflowId -ne $null){
            $execWFReq = New-Object Microsoft.Crm.Sdk.Messages.ExecuteWorkflowRequest
            $execWFReq.EntityId = $Id
            $execWFReq.WorkflowId=$WorkflowId
            $result = $conn.ExecuteCrmOrganizationRequest($execWFReq) 
        }
        else{ throw "Duplicate workflow detected, try executing the workflow by its ID"}
        if($result -eq $null)
        {
            throw LastCrmConnectorException($conn)
        }
    }
    catch
    {
        throw LastCrmConnectorException($conn)
    }
    return $result
}

#Alias for Get-MyCrmUserId
New-Alias -Name Get-CrmCurrentUserId -Value Get-MyCrmUserId

#GetMyCrmUserId
function Get-MyCrmUserId{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml
    [CmdletBinding()]
    PARAM( 
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn
    )

    $conn = VerifyCrmConnectionParam $conn

    try
    {
        $result = $conn.GetMyCrmUserId()
        if($result -eq $null) 
        {
            throw LastCrmConnectorException($conn)
        }
    }
    catch
    {
        throw LastCrmConnectorException($conn)
    }    

    return $result
}

#GetAllAttributesForEntity
function Get-CrmEntityAttributes{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml
 [CmdletBinding()]
    PARAM( 
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn,
        [parameter(Mandatory=$true, Position=1)]
        [string]$EntityLogicalName
    )

    $conn = VerifyCrmConnectionParam $conn 
       
    try
    {
        $result = $conn.GetAllAttributesForEntity($EntityLogicalName)
        if($result -eq $null)
        {
            throw LastCrmConnectorException($conn)
        }
    }
    catch
    {
        throw LastCrmConnectorException($conn)
    }    

    return $result
}

#GetAllEntityMetadata
function Get-CrmEntityAllMetadata{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml
 [CmdletBinding()]
    PARAM( 
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn,
        [parameter(Mandatory=$false, Position=1)]
        [bool]$OnlyPublished=$true, 
        [parameter(Mandatory=$false, Position=2)]
        [string]$EntityFilters
    )
    $conn = VerifyCrmConnectionParam $conn 
    switch($EntityFilters.ToLower())
    {
        "all" {
            $filter = [Microsoft.Xrm.Sdk.Metadata.EntityFilters]::All
            break             
        }
        "attributes" {
            $filter = [Microsoft.Xrm.Sdk.Metadata.EntityFilters]::Attributes
            break 
        } 
        "entity" {
            $filter = [Microsoft.Xrm.Sdk.Metadata.EntityFilters]::Entity
            break
        }  
        "privileges" {
            $filter = [Microsoft.Xrm.Sdk.Metadata.EntityFilters]::Privileges
            break
        }  
        "relationships" {
            $filter = [Microsoft.Xrm.Sdk.Metadata.EntityFilters]::Relationships
            break
        }
        default {
            $filter = [Microsoft.Xrm.Sdk.Metadata.EntityFilters]::Default
            break
        }               
    }
    try
    {
        $result = $conn.GetAllEntityMetadata($OnlyPublished, $filter)
        if($result -eq $null)
        {
            throw LastCrmConnectorException($conn)
        }
    }
    catch
    {
        throw LastCrmConnectorException($conn)
    }    
    return $result
}

#GetEntityAttributeMetadataForAttribute
function Get-CrmEntityAttributeMetadata{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml
 [CmdletBinding()]
    PARAM( 
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn,
        [parameter(Mandatory=$true, Position=1)]
        [string]$EntityLogicalName,
        [parameter(Mandatory=$true, Position=2)]
        [string]$FieldLogicalName
    )

    $conn = VerifyCrmConnectionParam $conn 
    
    try
    {
        $result = $conn.GetEntityAttributeMetadataForAttribute($EntityLogicalName, $FieldLogicalName)
        if($result -eq $null)
        {
            throw LastCrmConnectorException($conn)
        }
    }
    catch
    {
        throw LastCrmConnectorException($conn)
    }    
    return $result
}

#GetEntityDataByFetchSearch
function Get-CrmRecordsByFetch{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml
    [CmdletBinding()]
    PARAM(
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn,
        [parameter(Mandatory=$true, Position=1)]
        [string]$Fetch,
        [parameter(Mandatory=$false, Position=2)]
        [int]$TopCount,
        [parameter(Mandatory=$false, Position=3)]
        [int]$PageNumber,
        [parameter(Mandatory=$false, Position=4)]
        [string]$PageCookie,
        [parameter(Mandatory=$false, Position=5)]
        [switch]$AllRows
    )
        $elapsed = [System.Diagnostics.Stopwatch]::StartNew() 
        $conn = VerifyCrmConnectionParam $conn
        #default page number to 1 if not supplied
        if($PageNumber -eq 0)
        {
            $PageNumber = 1
        }
        $PagingCookie = ""
        $NextPage = $false
        if($PageCookie -eq "")
        {
            $PageCookie = $null
        }
        #$recordslist = New-Object 'System.Collections.Generic.List[System.Management.Automation.PSObject]'
        $recordslist = New-Object "System.Collections.Generic.List[PSCustomObject]"
        $resultSet = New-Object 'System.Collections.Generic.Dictionary[[System.String],[System.Management.Automation.PSObject]]'
        try
        {
            Write-Debug "Getting data from CRM"
            $xml = [xml]$Fetch
            if($xml.fetch.count -ne 0 -and $TopCount -eq 0)
            {
                $TopCount = $xml.fetch.count
            }
            $crmFetchTimer = [System.Diagnostics.Stopwatch]::StartNew() 
            $records = $conn.GetEntityDataByFetchSearch($Fetch, $TopCount, $PageNumber, $PageCookie, [ref]$PagingCookie, [ref]$NextPage, [Guid]::Empty)
            $crmFetchTimer.Stop() 
            if($conn.LastCrmException -ne $null)
            {
                throw LastCrmConnectorException($conn)
            }
            $swCheck= [System.Diagnostics.Stopwatch]::StartNew() 
            $logicalname = $xml.SelectSingleNode("/fetch/entity").Name
            #if there are zero results returned
            if($records.Count -eq 0)
            {
                $error = "No Result" 
                Write-Warning $error
                $resultSet.Add("CrmRecords", $recordslist)
                $resultSet.Add("Count", $recordslist.Count)
                $resultSet.Add("PagingCookie",$null)
                $resultSet.Add("NextPage",$false)
                $resultSet.Add("FetchXml", $Fetch)
                $resultSet.Add("FetchQueryTime", $crmFetchTimer.Elapsed) 
                #EXIT
                return $resultSet
            }
            #if we have records
            elseif($records.Count -gt 0)
            {
                Write-Verbose "$($records.Count) records Found!"
                $swElapsed=[System.Diagnostics.Stopwatch]::StartNew() 
                [System.Collections.Generic.List[PSCustomObject]]$recordslist = parseRecordsPage -records $records -logicalname $logicalname -xml $xml -Verbose
                Write-Verbose "Elapsed Time: $($swElapsed.ElapsedMilliseconds)"
                #IF we have multiple pages!
                if($NextPage -and $AllRows)  
                {
                    $PageNumber = $PageNumber + 1
                    Write-Debug "Fetching next page #$PageNumber"
                    #TODO: restructure to avoid recursion for multiple pages
                    $crmFetchTimer.Start()  
                    $NextRecordSet = Get-CrmRecordsByFetch -conn $conn -Fetch $Fetch -TopCount $TopCount -PageNumber $PageNumber -PageCookie $PagingCookie -AllRows
                    $crmFetchTimer.Stop()
                    if($NextRecordSet.CrmRecords.Count -gt 0)
                    {
                        Write-Verbose "Adding data to original results from page#: $PageNumber"
                        $recordslist.AddRange($NextRecordSet.CrmRecords)
                    }
                }
            }
        }
        catch
        {
            Write-Error $_.Exception
            throw LastCrmConnectorException($conn)
        }
        $resultSet = New-Object 'System.Collections.Generic.Dictionary[[System.String],[System.Management.Automation.PSObject]]'
        $resultSet.Add("CrmRecords", $recordslist)
        $resultSet.Add("Count", $recordslist.Count)
        $resultSet.Add("PagingCookie",$PagingCookie)
        $resultSet.Add("NextPage",$NextPage)
        $resultSet.Add("FetchXml", $Fetch)
        $resultSet.Add("FetchQueryTime", $crmFetchTimer.Elapsed) 
        Write-Verbose "Crm Server responded in: $($crmFetchTimer.Elapsed)" 
        Write-Verbose "Get-CrmRecordsByFetch Parse: $($swCheck.Elapsed)" 
        Write-Verbose "Get-CrmRecordsByFetch Total: $($elapsed.Elapsed)" 
        return $resultSet
}

#GetEntityDisplayName
function Get-CrmEntityDisplayName{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml
 [CmdletBinding()]
    PARAM( 
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn,
        [parameter(Mandatory=$true, Position=1, ParameterSetName="EntityLogicalName")]
        [string]$EntityLogicalName
    )
    $conn = VerifyCrmConnectionParam $conn 
    try
    {
        $result = $conn.GetEntityDisplayName($EntityLogicalName)
        if($result -eq $null)
        {
            throw LastCrmConnectorException($conn)
        }
    }
    catch
    {
        throw LastCrmConnectorException($conn)
    }    
    return $result
}

#GetEntityDisplayNamePlural
function Get-CrmEntityDisplayPluralName{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml
 [CmdletBinding()]
    PARAM( 
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn,
        [parameter(Mandatory=$true, Position=1)]
        [string]$EntityLogicalName
    )
    $conn = VerifyCrmConnectionParam $conn 
    try
    {
        $result = $conn.GetEntityDisplayNamePlural($EntityLogicalName)
        if($result -eq $null)
        {
            throw LastCrmConnectorException($conn)
        }
    }
    catch
    {
        throw LastCrmConnectorException($conn)
    }     
    return $result
}

#GetEntityMetadata
function Get-CrmEntityMetadata{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml
 [CmdletBinding()]
    PARAM( 
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn,
        [parameter(Mandatory=$true, Position=1)]
        [string]$EntityLogicalName,
        [parameter(Mandatory=$false, Position=2)]
        [string]$EntityFilters
    )

    $conn = VerifyCrmConnectionParam $conn 

    switch($EntityFilters.ToLower())
    {
        "all" {
            $filter = [Microsoft.Xrm.Sdk.Metadata.EntityFilters]::All
            break             
        }
        "attributes" {
            $filter = [Microsoft.Xrm.Sdk.Metadata.EntityFilters]::Attributes
            break 
        } 
        "entity" {
            $filter = [Microsoft.Xrm.Sdk.Metadata.EntityFilters]::Entity
            break
        }  
        "privileges" {
            $filter = [Microsoft.Xrm.Sdk.Metadata.EntityFilters]::Privileges
            break
        }  
        "relationships" {
            $filter = [Microsoft.Xrm.Sdk.Metadata.EntityFilters]::Relationships
            break
        }
        default {
            $filter = [Microsoft.Xrm.Sdk.Metadata.EntityFilters]::Default
            break
        }               
    }

    try
    {
        $result = $conn.GetEntityMetadata($EntityLogicalName, $filter)
        if($result -eq $null)
        {
            throw LastCrmConnectorException($conn)
        }
    }
    catch
    {
        throw LastCrmConnectorException($conn)
    }    

    return $result
}

#GetEntityName
function Get-CrmEntityName{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml
 [CmdletBinding()]
    PARAM( 
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn,
        [parameter(Mandatory=$true, Position=1)]
        [int]$EntityTypeCode
    )

    $conn = VerifyCrmConnectionParam $conn  

    try
    {
        $result = $conn.GetEntityName($EntityTypeCode)
        if($result -eq $null)
        {
            throw LastCrmConnectorException($conn)
        }
    }
    catch
    {
        throw LastCrmConnectorException($conn)
    }   

    return $result
}

#GetEntityTypeCode
function Get-CrmEntityTypeCode{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml
 [CmdletBinding()]
    PARAM( 
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn,
        [parameter(Mandatory=$true, Position=1)]
        [string]$EntityLogicalName
    )
    $conn = VerifyCrmConnectionParam $conn  
    try
    {
        $result = $conn.GetEntityTypeCode($EntityLogicalName)
        if($result -eq $null)
        {
            throw LastCrmConnectorException($conn)
        }
    }
    catch
    {
        throw LastCrmConnectorException($conn)
    }    
    return $result
}

#GetGlobalOptionSetMetadata
function Get-CrmGlobalOptionSet{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml
 [CmdletBinding()]
    PARAM( 
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn,
        [parameter(Mandatory=$true, Position=1)]
        [string]$OptionSetName
    )
    $conn = VerifyCrmConnectionParam $conn  
    try
    {
        $result = $conn.GetGlobalOptionSetMetadata($OptionSetName)
        if($result -eq $null)
        {
            throw LastCrmConnectorException($conn)
        }
    }
    catch
    {
        throw LastCrmConnectorException($conn)
    }
    return $result
}

#GetPickListElementFromMetadataEntity
function Get-CrmEntityOptionSet{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml
 [CmdletBinding()]
    PARAM( 
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn,
        [parameter(Mandatory=$true, Position=1)]
        [string]$EntityLogicalName,
        [parameter(Mandatory=$true, Position=2)]
        [string]$FieldLogicalName
    )
    $conn = VerifyCrmConnectionParam $conn
    try
    {
        $result = $conn.GetPickListElementFromMetadataEntity($EntityLogicalName, $FieldLogicalName)
        if($result -eq $null)
        {
            throw LastCrmConnectorException($conn)
        }
    }
    catch
    {
        throw LastCrmConnectorException($conn)
    }

    return $result
}

#ImportSolutionToCrmAsync
function Import-CrmSolutionAsync{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml
    [CmdletBinding()]
    PARAM(
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn,
        [parameter(Mandatory=$true, Position=1)]
        [string]$SolutionFilePath,
        [parameter(Mandatory=$false, Position=2)]
        [switch]$ActivateWorkflows,
        [parameter(Mandatory=$false, Position=3)]
        [switch]$OverwriteUnManagedCustomizations,
        [parameter(Mandatory=$false, Position=4)]
        [switch]$SkipDependancyOnProductUpdateCheckOnInstall, 
        [parameter(Mandatory=$false, Position=5)]
        [switch]$PublishChanges,
        [parameter(Mandatory=$false, Position=6)]
        [int64]$MaxWaitTimeInSeconds,
        [parameter(Mandatory=$false, Position=7)]
        [switch]$ImportAsHoldingSolution, 
        [parameter(Mandatory=$false, Position=8)]
        [switch]$BlockUntilImportComplete
    )
    $conn = VerifyCrmConnectionParam $conn
    $importId = [guid]::Empty
    $asyncResponse = $null
    try
    {
        if (!(Test-Path $SolutionFilePath)) 
        {
            throw [System.IO.FileNotFoundException] "$SolutionFilePath not found."
        }
        
        Write-Output  "Importing solution file $SolutionFilePath into: $($conn.CrmConnectOrgUriActual)" 
        Write-Verbose "OverwriteCustomizations: $OverwriteUnManagedCustomizations"
        Write-Verbose "SkipDependancyCheck: $SkipDependancyOnProductUpdateCheckOnInstall"
        Write-Verbose "ImportAsHoldingSolution: $ImportAsHoldingSolution"
        Write-Verbose "Maximum seconds to poll: $MaxWaitTimeInSeconds"
        Write-Verbose "Block and wait? $BlockUntilImportComplete"

        if($BlockUntilImportComplete -eq $false -and ($MaxWaitTimeInSeconds -gt 0 -or $MaxWaitTimeInSeconds -eq -1)){
            Write-Warning "MaxWaitTimeInSeconds is $MaxWaitTimeInSeconds, we assume the user wants to block until complete."
            Write-Warning "To avoid this warning in the future please specify the switch: -BlockUntilImportComplete"
            $BlockUntilImportComplete = $true
        }
        if($BlockUntilImportComplete -eq $false -and $PublishChanges -eq $true){
            Write-Warning "PublishChanges will be ignored because BlockUntilImportComplete is $BlockUntilImportComplete"
            $PublishChanges = $false; 
        }

        $data = [System.IO.File]::ReadAllBytes($SolutionFilePath)
        
        $request = New-Object Microsoft.Crm.Sdk.Messages.ImportSolutionRequest
        $request.CustomizationFile = $data  
        $request.PublishWorkflows = $ActivateWorkflows
        $request.OverwriteUnmanagedCustomizations = $OverwriteUnManagedCustomizations
        $request.SkipProductUpdateDependencies = $SkipDependancyOnProductUpdateCheckOnInstall
        $request.HoldingSolution = $ImportAsHoldingSolution

        $asyncRequest = New-Object Microsoft.Xrm.Sdk.Messages.ExecuteAsyncRequest
        $asyncRequest.Request = $request; 

        Write-Verbose "ExecuteCrmOrganizationRequest with ExecuteAsyncRequest containing ImportSolutionRequest() this process can take a while..."
        try
        {
            $asyncResponse = ($conn.ExecuteCrmOrganizationRequest($asyncRequest, "AsyncImportRequest") -as [Microsoft.Xrm.Sdk.Messages.ExecuteAsyncResponse]) 
            $importId = $asyncResponse.AsyncJobId
            
            Write-Verbose "ImportId (asyncoperationid): $importId" 
            if($importId -eq $null -or $importId -eq [Guid]::Empty)
            {
                throw "Import request failed, asyncoperationid is: $importId"
            }
            #if the caller wants to get the ID and does NOT want to wait
            if($BlockUntilImportComplete -eq $false){
                return $asyncResponse; 
            }
        }
        catch
        {
            throw LastCrmConnectorException($conn)
        }    
        $pollingStart = Get-Date
        $isProcessing = $true
        $secondsSpentPolling = 0
        $pollingDelaySeconds = 5
        $transientFailureCount = 0; 
        Write-Verbose "Import of file completed, waiting on completion of AsyncOperationId: $importId"

        try{
            while(($isProcessing -and $secondsSpentPolling -lt $MaxWaitTimeInSeconds) -or ($isProcessing -and $MaxWaitTimeInSeconds -eq -1)){
                #delay
                Start-Sleep -Seconds $pollingDelaySeconds
                #check the import job for success/fail/inProgress
                try{
                    $import = Get-CrmRecord -conn $conn -EntityLogicalName asyncoperation -Id $importId -Fields statuscode
                } catch {
                    $transientFailureCount++; 
                    Write-Verbose "Import Job status check did not succeed: $($_.Exception)"
                }
                $status = $import.statuscode_Property.value.Value; 
                #Check for import completion - https://msdn.microsoft.com/en-us/library/gg309288.aspx
                if($status -lt 30){
                    $isProcessing = $true
                    $secondsSpentPolling = ([Int]((Get-Date) - $pollingStart).TotalSeconds)
                    Write-Output "$($secondsSPentPolling.ToString("000")) sec of: $MaxWaitTimeInSeconds - ImportStatus: $($import.statuscode)"
                }
                elseif($status -eq 31 -or $status -eq 32 ){
                    $isProcessing = $false
                    throw "$($import.statuscode) - AsyncOperation with Id: $importId has been either cancelled or has failed."
                    break; 
                }
                elseif($status -eq 30){
                    $isProcessing = $false
                    Write-Verbose "Processing Completed at: $($import.completedon)" 
                    if($PublishChanges){
                        Write-Verbose "PublishChanges set, executing: Publish-CrmAllCustomization using the same connection."
                        Publish-CrmAllCustomization -conn $conn
                        return $asyncResponse
                    }
                    else{
                        Write-Output "Import Complete, don't forget to publish customizations."
                        return $asyncResponse
                    }
                    break; 
                }
            }
            #User provided timeout and exit function with an error
            if($secondsSpentPolling -gt $MaxWaitTimeInSeconds){
                Write-Warning "Import-CrmSolutionAsync exited due to exceeding the maximum timeout of $MaxWaitTimeInSeconds. The import will continue in CRM async until it either succeeds or fails."
            }
            #at this point we appear to have imported successfully
            return $asyncResponse; 
        } Catch {
            throw "AsyncOperation with ID: $importId has encountered an exception: $_"
        }
    }
    catch
    {
        throw $_.Exception
    }    
}

#ImportSolutionToCrm
function Import-CrmSolution{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml
    [CmdletBinding()]
    PARAM(
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn,
        [parameter(Mandatory=$true, Position=1)]
        [string]$SolutionFilePath,
        [parameter(Mandatory=$false, Position=2)]
        [switch]$ActivatePlugIns,
        [parameter(Mandatory=$false, Position=3)]
        [switch]$OverwriteUnManagedCustomizations,
        [parameter(Mandatory=$false, Position=4)]
        [switch]$SkipDependancyOnProductUpdateCheckOnInstall, 
        [parameter(Mandatory=$false, Position=5)]
        [switch]$PublishChanges,
        [parameter(Mandatory=$false, Position=6)]
        [int64]$MaxWaitTimeInSeconds = 900,
        [parameter(Mandatory=$false, Position=7)]
        [switch]$ImportAsHoldingSolution, 
        [parameter(Mandatory=$false, Position=8)]
        [switch]$AsyncOperationImportMethod 
    )
    $conn = VerifyCrmConnectionParam $conn
    $importId = [guid]::Empty
    try
    {
        if (!(Test-Path $SolutionFilePath)) 
        {
            throw [System.IO.FileNotFoundException] "$SolutionFilePath not found."
        }
        Write-Host "Importing solution file $SolutionFilePath into: $($conn.CrmConnectOrgUriActual)" 
        Write-Verbose "OverwriteCustomizations: $OverwriteUnManagedCustomizations"
        Write-Verbose "SkipDependancyCheck: $SkipDependancyOnProductUpdateCheckOnInstall"
        Write-Verbose "ImportAsHoldingSolution: $ImportAsHoldingSolution"
        Write-Verbose "Maximum seconds to poll for successful completion: $MaxWaitTimeInSeconds"

        if($AsyncOperationImportMethod){
            Write-Warning "Option to import using Aync method flagged! With this option you may not be able to debug problems easily."
            Write-Warning "To import async going forward use the Import-CrmSolutionAsync cmdlet instead."
            $result = Import-CrmSolutionAsync `
                -Conn $conn `
                -SolutionFilePath $SolutionFilePath `
                -OverwriteUnManagedCustomizations:$OverwriteUnManagedCustomizations `
                -SkipDependancyOnProductUpdateCheckOnInstall:$SkipDependancyOnProductUpdateCheckOnInstall `
                -MaxWaitTimeInSeconds $MaxWaitTimeInSeconds `
                -ImportAsHoldingSolution:$ImportAsHoldingSolution `
                -BlockUntilImportComplete:$true; 

            Write-Verbose "Solution import using async completed - asyncoperationid = $($result.AsyncJobId)"; 
            return $result; 
        }

        Write-Verbose "Calling .ImportSolutionToCrm() this process can take minutes..."
        $result = $conn.ImportSolutionToCrm($SolutionFilePath, [ref]$importId, $ActivatePlugIns,
                $OverwriteUnManagedCustomizations, $SkipDependancyOnProductUpdateCheckOnInstall,$ImportAsHoldingSolution)
        Write-Verbose "ImportId: $result" 
        if ($result -eq [guid]::Empty) {
             throw LastCrmConnectorException($conn)
        }
        $pollingStart = Get-Date
        $isProcessing = $true
        $secondsSpentPolling = 0
        $pollingDelaySeconds = 5
        $TopPrevProcPercent = [double]0
        $isProcPercentReduced = $false
        #this is for a bug where the service will throw a 401 on retrieve of importjob during an import under certain conditions
        $transientFailureCount = 0; 
        Write-Host "Import of file completed, waiting on completion of importId: $importId"
        try{
            while($isProcessing -and $secondsSpentPolling -lt $MaxWaitTimeInSeconds){
                #delay
                Start-Sleep -Seconds $pollingDelaySeconds
                #check the import job for success/fail/inProgress
                try{
                    $import = Get-CrmRecord -conn $conn -EntityLogicalName importjob -Id $importId -Fields solutionname,data,completedon,startedon,progress
                } catch {
                    if($transientFailureCount > 5){
                        Write-Error "Import Job status check FAILED 5 times this could be due to a bug where the service returns a 401. Throwing lastException:"; 
                        throw  $conn.LastCrmException
                    }
                    Write-Verbose "Import Job status check FAILED this could be due to a bug where the service returns a 401. We'll allow up to 5 failures before aborting."; 
                    $transientFailureCount++; 
                }
                #Option to use Get-CrmRecords so we can force a no-lock to prevent hangs in the retrieve
                #$import = (Get-CrmRecords -conn $conn -EntityLogicalName importjob -FilterAttribute importjobid -FilterOperator eq -FilterValue $importId -Fields data,completedon,startedon,progress).CrmRecords[0]
                $importManifest = ([xml]($import).data).importexportxml.solutionManifests.solutionManifest
                $ProcPercent = [double](Coalesce $import.progress "0")

                #check if processing percentage reduced at any given time
                if($TopPrevProcPercent -gt $ProcPercent)
                {
                    $isProcPercentReduced = $true
                    Write-Verbose "Processing is reversing... import will fail."
                } else {
                    $TopPrevProcPercent = $ProcPercent
                }

                #Check for import completion
                if($import.completedon -eq $null -and $importManifest.result.result -ne "success"){
                    $isProcessing = $true
                    $secondsSpentPolling = ([Int]((Get-Date) - $pollingStart).TotalSeconds)
                    Write-Host "$($secondsSPentPolling.ToString("000")) seconds of max: $MaxWaitTimeInSeconds ... ImportJob%: $ProcPercent"
                }
                else {
                    Write-Verbose "Processing Completed at: $($import.completedon) with ImportJob%: $ProcPercent" 
                    Write-Verbose "Import Manifest Result: $($importManifest.result.result) with ImportJob%: $ProcPercent"                     

                    $solutionImportResults =  Select-Xml -Xml ([xml]$import.data) -XPath "//result"
                    $anyFailuresInImport = $false;
                    $allErrorText = "";

                    foreach($solutionImportResult in $solutionImportResults)
                    {
                        try{
                            $resultParent = ""
                            $itemResult = ""
                            $resultParent = $($solutionImportResult.Node.ParentNode.ParentNode.Name)
                            $itemResult = $($solutionImportResult.Node.result)
                        }catch{}

                        Write-Verbose "Item:$resultParent result: $itemResult" # write each item result in result data
                        
                        if ($solutionImportResult.Node.result -ne "success")
                        {
                            # if any error in result print more error details
                            try{
                                $errorCode = ""
                                $errorText = ""
                                $moreErrorDetails = ""
                                $errorCode = $($solutionImportResult.Node.errorcode)
                                $errorText = $($solutionImportResult.Node.errortext)
                                $moreErrorDetails = $solutionImportResult.Node.parameters.InnerXml

                            }catch{}

                            Write-Verbose "errorcode: $errorCode errortext: $errorText more details: $moreErrorDetails"
                            if ($solutionImportResult.Node.result -eq "failure") # Fail only on errors, not on warnings
                            {
                                $anyFailuresInImport = $true; # mark if any failures in solution import
                                $allErrorText = $allErrorText + ";" + $errorText;
                            }
                        }
                    }
                    if(-not $isProcPercentReduced -and $importManifest.result.result -eq "success" -and (-not $anyFailuresInImport))
                    {
                        Write-Verbose "Setting to 100% since all results are success"
                        $ProcPercent = 100.0
                    }                    
                    $isProcessing = $false
                    break
                }
            }
        } Catch {
            Write-Error "ImportJob with ID: $importId has encountered an exception: $_ "
        } Finally{
            $ProcPercent = ([double](Coalesce $ProcPercent 0))
        }
        #User provided timeout and exit function with an error
        if($secondsSpentPolling -gt $MaxWaitTimeInSeconds){
            throw "Import-CrmSolution halted due to exceeding the maximum timeout of $MaxWaitTimeInSeconds."
        }
        #detect a failure by a failure result OR the percent being less than 100%
        if(($importManifest.result.result -eq "failure") -or ($ProcPercent -lt 100) -or $anyFailuresInImport) #Must look at %age instead of this result as the result is usually wrong!
        {
            Write-Verbose "Import result: failed - job with ID: $importId failed at $ProcPercent complete."
            throw $allErrorText
        }
        else
        {
            #at this point we appear to have imported successfully
            $managedsolution = $importManifest.Managed
            if($managedsolution -ne 1)
            {
                if($PublishChanges){
                    Write-Verbose "PublishChanges set, executing: Publish-CrmAllCustomization using the same connection."
                    Publish-CrmAllCustomization -conn $conn
                }
                else{
                    Write-Output "Import Complete, don't forget to publish customizations."
                }
            }
            else{
                #managed
                Write-Output "Import of managed solution complete."
            }
        }
    }
    catch
    {
        Write-Error $_.Exception
    }    
}

#InstallSampleDataToCrm
function Add-CrmSampleData{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml
    [CmdletBinding()]
    PARAM(
        [parameter(Mandatory=$false, Position=0)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn
    )
    $conn = VerifyCrmConnectionParam $conn 
    try
    {
        $result = $conn.InstallSampleDataToCrm()
        if($result -eq $null)
        {
            throw LastCrmConnectorException($conn)
        }
    }
    catch
    {
        throw LastCrmConnectorException($conn)
    }
    return $result
}

#IsSampleDataInstalled
function Test-CrmSampleDataInstalled{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml
    [CmdletBinding()]
    PARAM(
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn
    )

    $conn = VerifyCrmConnectionParam $conn 

    try
    {
        $result = $conn.IsSampleDataInstalled()
    }
    catch
    {
        throw LastCrmConnectorException($conn)
    }    

    return $result
}

#PublishEntity
function Publish-CrmEntity{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml
 [CmdletBinding()]
    PARAM( 
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn,
        [parameter(Mandatory=$true, Position=1)]
        [string]$EntityLogicalName
    )

    $conn = VerifyCrmConnectionParam $conn  

    try
    {
        $result = $conn.PublishEntity($EntityLogicalName)
        if(!$result)
        {
            throw LastCrmConnectorException($conn)
        }
    }
    catch
    {
        throw LastCrmConnectorException($conn)
    }

    return $result
}

#PublishTheme
function Publish-CrmTheme{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml
 [CmdletBinding()]
    PARAM( 
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn,
        [parameter(Mandatory=$true, Position=1, ParameterSetName="ThemeName")]
        [string]$ThemeName,
        [parameter(Mandatory=$true, Position=1, ParameterSetName="ThemeId")]
        [guid]$ThemeId
    )

    $conn = VerifyCrmConnectionParam $conn  

    try
    {
        if($ThemeName -ne "")
        {
            $themes = Get-CrmRecords -conn $conn -EntityLogicalName theme -FilterAttribute name -FilterOperator eq -FilterValue $ThemeName -WarningAction SilentlyContinue
            if($themes.CrmRecords.Count -eq 0)
            {
                Write-Warning "No Theme found"
                return
            }
            else
            {
                $ThemeId = $themes.CrmRecords[0].themeid
            }
        }
        $req = New-Object Microsoft.Crm.Sdk.Messages.PublishThemeRequest
        $req.target = New-CrmEntityReference -EntityLogicalName "theme" -Id $ThemeId
        $result = $conn.ExecuteCrmOrganizationRequest($req, $null)
        if(!$result)
        {
            throw LastCrmConnectorException($conn)
        }
    }
    catch
    {
        throw LastCrmConnectorException($conn)
    }

    return $result
}

#ResetLocalMetadataCache
function Remove-CrmEntityMetadataCache{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml
 [CmdletBinding()]
    PARAM( 
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn,
        [parameter(Mandatory=$false, Position=1)]
        [string]$EntityLogicalName
    )

    $conn = VerifyCrmConnectionParam $conn  

    if($EntityLogicalName -eq "")
    {
        $EntityLogicalName = $null
    }
    
    try
    {
        $result = $conn.ResetLocalMetadataCache($EntityLogicalName)
        if($result -eq $null)
        {
            throw LastCrmConnectorException($conn)
        }
    }
    catch
    {
        throw LastCrmConnectorException($conn)
    }    
}

#UninstallSampleDataFromCrm
function Remove-CrmSampleData{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml
    [CmdletBinding()]
    PARAM(
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn
    )

    $conn = VerifyCrmConnectionParam $conn 

    try
    {
        $result = $conn.UninstallSampleDataFromCrm()
    }
    catch
    {
        throw LastCrmConnectorException($conn)
    }

    return $result
}

#UpdateStateAndStatusForEntity
function Set-CrmRecordState{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml
    [CmdletBinding()]
    PARAM(
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn,
        [parameter(Mandatory=$true, Position=1, ParameterSetName="CrmRecord", ValueFromPipeline=$true)]
        [PSObject]$CrmRecord,
        [parameter(Mandatory=$true, Position=1, ParameterSetName="NameWithId")]
        [string]$EntityLogicalName,
        [parameter(Mandatory=$true, Position=2, ParameterSetName="NameWithId")]
        [guid]$Id,
        [parameter(Mandatory=$true, Position=3)]
        [string]$StateCode,
        [parameter(Mandatory=$true, Position=4)]
        [string]$StatusCode
    )

    begin
    {
        $conn = VerifyCrmConnectionParam $conn
    }     

    process
    {
        if($CrmRecord -ne $null)
        {
            $EntityLogicalName = $CrmRecord.logicalname
            #$Id = $CrmRecord.($EntityLogicalName + "id")
            $Id = $CrmRecord.'ReturnProperty_Id'
        }

        # Try to parse into int
        $StateCodeInt = 0
        $StatusCodeInt = 0
        
        try
        {
            if([int32]::TryParse($StateCode, [ref]$StateCodeInt) -and [int32]::TryParse($StatusCode, [ref]$StatusCodeInt))
            {
                $result = $conn.UpdateStateAndStatusForEntity($EntityLogicalName, $Id, $StateCodeInt, $statusCodeInt, [Guid]::Empty)
                if(!$result)
                {
                    throw LastCrmConnectorException($conn)
                }
            }
            else
            {
                $result = $conn.UpdateStateAndStatusForEntity($EntityLogicalName, $Id, $stateCode, $statusCode, [Guid]::Empty)
                if(!$result)
                {
                    throw LastCrmConnectorException($conn)
                }
            }
        }
        catch
        {
            throw LastCrmConnectorException($conn)
        }
    }
}

function Add-CrmSecurityRoleToTeam{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml

    [CmdletBinding()]
    PARAM(
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn,
        [parameter(Mandatory=$true, Position=1, ParameterSetName="CrmRecord")]
        [PSObject]$TeamRecord,
        [parameter(Mandatory=$false, Position=2, ParameterSetName="CrmRecord")]
        [PSObject]$SecurityRoleRecord,
        [parameter(Mandatory=$true, Position=1, ParameterSetName="Id")]
        [string]$TeamId,
        [parameter(Mandatory=$false, Position=2, ParameterSetName="Id")]
        [string]$SecurityRoleId,
        [parameter(Mandatory=$false, Position=2)]
        [string]$SecurityRoleName
    )

    $conn = VerifyCrmConnectionParam $conn

    if($SecurityRoleRecord -eq $null -and $SecurityRoleId -eq "" -and $SecurityRoleName -eq "")
    {
        Write-Warning "You need to specify Security Role information"
        return
    }
    
    if($SecurityRoleName -ne "")
    {
        if($TeamRecord -eq $null -or $TeamRecord.businessunitid -eq $null)
        {
            $TeamRecord = Get-CrmRecord -conn $conn -EntityLogicalName team -Id $TeamId -Fields businessunitid
        }

        $fetch = @"
        <fetch version="1.0" output-format="xml-platform" mapping="logical" distinct="false" no-lock="true">
          <entity name="role">
            <attribute name="businessunitid" />
            <attribute name="roleid" />
            <filter type="and">
              <condition attribute="name" operator="eq" value="{0}" />
              <condition attribute="businessunitid" operator="eq" value="{1}" />
            </filter>
          </entity>
        </fetch>
"@
 -F $SecurityRoleName, $TeamRecord.businessunitid_Property.Value.Id
        
        $roles = (Get-CrmRecordsByFetch -conn $conn -Fetch $fetch)
        if($roles.CrmRecords.Count -eq 0)
        {
            Write-Warning "Not Security Role found"
            return
        }
        else
        {
            $role = $roles.CrmRecords[0]
        }
    }

    if($SecurityRoleName -ne "")
    {
        Add-CrmRecordAssociation -conn $conn -CrmRecord1 $TeamRecord -CrmRecord2 $role -RelationshipName teamroles_association
    }
    elseif($TeamRecord -ne $null)
    {
        Add-CrmRecordAssociation -conn $conn -CrmRecord1 $TeamRecord -CrmRecord2 $SecurityRoleRecord -RelationshipName teamroles_association
    }
    else
    {
        Add-CrmRecordAssociation -conn $conn -EntityLogicalName1 team -Id1 $TeamId -EntityLogicalName2 role -Id2 $SecurityRoleId -RelationshipName teamroles_association
    }
}

### Other Cmdlets added by Dynamics CRM PFE ###
function Add-CrmSecurityRoleToUser{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml

    [CmdletBinding()]
    PARAM(
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn,
        [parameter(Mandatory=$true, Position=1, ParameterSetName="CrmRecord")]
        [PSObject]$UserRecord,
        [parameter(Mandatory=$false, Position=2, ParameterSetName="CrmRecord")]
        [PSObject]$SecurityRoleRecord,
        [parameter(Mandatory=$true, Position=1, ParameterSetName="Id")]
        [string]$UserId,
        [parameter(Mandatory=$false, Position=2, ParameterSetName="Id")]
        [string]$SecurityRoleId,
        [parameter(Mandatory=$false, Position=2)]
        [string]$SecurityRoleName
    )

    $conn = VerifyCrmConnectionParam $conn

    if($SecurityRoleRecord -eq $null -and $SecurityRoleId -eq "" -and $SecurityRoleName -eq "")
    {
        Write-Warning "You need to specify Security Role information"
        return
    }
    
    if($SecurityRoleName -ne "")
    {
        if($UserRecord -eq $null -or $UserRecord.businessunitid -eq $null)
        {
            $UserRecord = Get-CrmRecord -conn $conn -EntityLogicalName systemuser -Id $UserId -Fields businessunitid
        }

        $fetch = @"
        <fetch version="1.0" output-format="xml-platform" mapping="logical" distinct="false" no-lock="true">
          <entity name="role">
            <attribute name="businessunitid" />
            <attribute name="roleid" />
            <filter type="and">
              <condition attribute="name" operator="eq" value="{0}" />
              <condition attribute="businessunitid" operator="eq" value="{1}" />
            </filter>
          </entity>
        </fetch>
"@
 -F $SecurityRoleName, $UserRecord.businessunitid_Property.Value.Id
        
        $roles = (Get-CrmRecordsByFetch -conn $conn -Fetch $fetch)
        if($roles.CrmRecords.Count -eq 0)
        {
            Write-Warning "Not Security Role found"
            return
        }
        else
        {
            $role = $roles.CrmRecords[0]
        }
    }

    if($SecurityRoleName -ne "")
    {
        Add-CrmRecordAssociation -conn $conn -CrmRecord1 $UserRecord -CrmRecord2 $role -RelationshipName systemuserroles_association
    }
    elseif($UserRecord -ne $null)
    {
        Add-CrmRecordAssociation -conn $conn -CrmRecord1 $UserRecord -CrmRecord2 $SecurityRoleRecord -RelationshipName systemuserroles_association
    }
    else
    {
        Add-CrmRecordAssociation -conn $conn -EntityLogicalName1 systemuser -Id1 $UserId -EntityLogicalName2 role -Id2 $SecurityRoleId -RelationshipName systemuserroles_association
    }
}

function Approve-CrmEmailAddress{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml

    [CmdletBinding()]
    PARAM(
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn,
        [parameter(Mandatory=$true, Position=1, ParameterSetName="UserId")]
        [string]$UserId,
        [parameter(Mandatory=$true, Position=1, ParameterSetName="QueueId")]
        [string]$QueueId
    )

    $conn = VerifyCrmConnectionParam $conn

    if($UserId -ne "")
    {
        Set-CrmRecord -conn $conn -EntityLogicalName systemuser -Id $UserId -Fields @{"emailrouteraccessapproval"=(New-CrmOptionSetValue 1)}
    }
    else
    {
        Set-CrmRecord -conn $conn -EntityLogicalName queue -Id $QueueId -Fields @{"emailrouteraccessapproval"=(New-CrmOptionSetValue 1)}
    }
}

function Disable-CrmLanguagePack{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml

    [CmdletBinding()]
    PARAM( 
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn,
        [parameter(Mandatory=$true, Position=1)]
        [Int]$LCID
    )

    $conn = VerifyCrmConnectionParam $conn  

    $request = New-Object Microsoft.Crm.Sdk.Messages.DeprovisionLanguageRequest
    $request.Language = $LCID
    
    try
    {
        $result = $conn.ExecuteCrmOrganizationRequest($request, $null)
        if($result -eq $null)
        {
            throw LastCrmConnectorException($conn)
        }
    }
    catch
    {
        throw LastCrmConnectorException($conn)
    }    
}

function Enable-CrmLanguagePack{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml

    [CmdletBinding()]
    PARAM( 
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn,
        [parameter(Mandatory=$true, Position=1)]
        [Int]$LCID
    )

    $conn = VerifyCrmConnectionParam $conn

    $request = New-Object Microsoft.Crm.Sdk.Messages.ProvisionLanguageRequest
    $request.Language = $LCID
    
    try
    {
        $result = $conn.ExecuteCrmOrganizationRequest($request, $null)
        if($result -eq $null)
        {
            throw LastCrmConnectorException($conn)
        }
    }
    catch
    {
        throw LastCrmConnectorException($conn)
    }    
}

function Export-CrmApplicationRibbonXml {
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml

    [CmdletBinding()]
    PARAM( 
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn,
        [parameter(Mandatory=$false, Position=1)][alias("Path")]        
        [string]$RibbonFilePath
    )

    $conn = VerifyCrmConnectionParam $conn
    
    $exportPath = if($RibbonFilePath -ne ""){Get-Item $RibbonFilePath} else {Get-Location}
    $exportFileName = "applicationRibbon.xml"
    $path = Join-Path $exportPath $exportFileName
    # Instantiate RetrieveEntityRibbonRequest
    $request = New-Object Microsoft.Crm.Sdk.Messages.RetrieveApplicationRibbonRequest
    
    try
    {
        $response = $conn.ExecuteCrmOrganizationRequest($request, $null)
        if ($response.CompressedApplicationRibbonXml -ne $null)
        {
            $ribbonXml = UnzipCrmRibbon -Data $response.CompressedApplicationRibbonXml
            
            Write-Verbose 'Saving ribbon file to path: $path'

            $ribbonXml.Save($path)

            Write-Verbose "Successfully wrote file"
            
            $result = New-Object PSObject
            Add-Member -InputObject $result -MemberType NoteProperty -Name "RetrieveApplicationRibbonRequest" -Value $response
            Add-Member -InputObject $result -MemberType NoteProperty -Name "RibbonFilePath" -Value $path
            return $result
        }

        #Should only get here if there was nothing returned.
        throw LastCrmConnectorException($conn)
    }
    catch
    {
        throw LastCrmConnectorException($conn)
    }
}

function Export-CrmEntityRibbonXml {
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml
 
    [CmdletBinding()]
    PARAM( 
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn,
        [parameter(Mandatory=$true, Position=1)]
        [string]$EntityLogicalName,
        [parameter(Mandatory=$false, Position=2)][alias("Path")]        
        [string]$RibbonFilePath
    )

    $conn = VerifyCrmConnectionParam $conn
    
    $exportPath = if($RibbonFilePath -ne ""){Get-Item $RibbonFilePath} else {Get-Location}
    $exportFileName = $EntityLogicalName + "Ribbon.xml"
    $path = Join-Path $exportPath $exportFileName
    # Instantiate RetrieveEntityRibbonRequest
    $request = New-Object Microsoft.Crm.Sdk.Messages.RetrieveEntityRibbonRequest
    $request.EntityName = $EntityLogicalName
    $request.RibbonLocationFilter = [Microsoft.Crm.Sdk.Messages.RibbonLocationFilters]::All
    
    try
    {
        $response = $conn.ExecuteCrmOrganizationRequest($request, $null)
        if ($response.CompressedEntityXml -ne $null)
        {
            $ribbonXml = UnzipCrmRibbon -Data $response.CompressedEntityXml
            
            Write-Verbose 'Saving ribbon file to path: $path'

            $ribbonXml.Save($path)

            Write-Verbose "Successfully wrote file"
            
            $result = New-Object PSObject
            Add-Member -InputObject $result -MemberType NoteProperty -Name "RetrieveEntityRibbonResponse" -Value $response
            Add-Member -InputObject $result -MemberType NoteProperty -Name "RibbonFilePath" -Value $path
            return $result
        }

        #Should only get here if there was nothing returned.
        throw LastCrmConnectorException($conn)
    }
    catch
    {
        throw LastCrmConnectorException($conn)
    }
}

function Export-CrmSolution{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml

    [CmdletBinding()]
    PARAM(
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn,
        [parameter(Mandatory=$true, Position=1)]
        [string]$SolutionName, 
        [parameter(Mandatory=$false, Position=2)]
        [string]$SolutionFilePath,
        [parameter(Mandatory=$false)]
        [string]$SolutionZipFileName,
        [parameter(Mandatory=$false)]
        [switch]$Managed,
        [parameter(Mandatory=$false)]
        [string]$TargetVersion, 
        [parameter(Mandatory=$false)]
        [switch]$ExportAutoNumberingSettings, 
        [parameter(Mandatory=$false)]
        [switch]$ExportCalendarSettings, 
        [parameter(Mandatory=$false)]
        [switch]$ExportCustomizationSettings, 
        [parameter(Mandatory=$false)]
        [switch]$ExportEmailTrackingSettings, 
        [parameter(Mandatory=$false)]
        [switch]$ExportGeneralSettings, 
        [parameter(Mandatory=$false)]
        [switch]$ExportMarketingSettings, 
        [parameter(Mandatory=$false)]
        [switch]$ExportOutlookSynchronizationSettings, 
        [parameter(Mandatory=$false)]
        [switch]$ExportRelationshipRoles, 
        [parameter(Mandatory=$false)]
        [switch]$ExportIsvConfig, 
        [parameter(Mandatory=$false)]
        [switch]$ExportSales
    )    

    $conn = VerifyCrmConnectionParam $conn

    try
    {
        $solutionRecords = (Get-CrmRecords -conn $conn -EntityLogicalName solution -FilterAttribute uniquename -FilterOperator "like" -FilterValue $SolutionName -Fields uniquename,publisherid,version )
        #if we can't find just one solution matching then ERROR
        if($solutionRecords.CrmRecords.Count -ne 1)
        {
            $friendlyName = $conn.ConnectedOrgFriendlyName.ToString()
            Write-Error "Solution with name `"$SolutionName`" in CRM Instance: `"$friendlyName`" not found!"
            break
        }
        #else PROCEED
        $crmSolutionRecord = $solutionRecords.CrmRecords[0]
        $version = $crmSolutionRecord.version
        $solutionUniqueName = $crmSolutionRecord.uniquename

        write-verbose "Solution found with version# $version"
        $exportPath = if($SolutionFilePath -ne ""){Get-Item $SolutionFilePath} else {Get-Location}
        #if a filename is not given, then we'll default one to [solutionname]_[managed]_[version].zip
        if($SolutionZipFileName.Length -eq 0)
        {
            $version = $version.Replace('.','_')
            $managedFileName = if($Managed) {"_managed_"} else {"_unmanaged_"}
            $solutionZipFileName = "$solutionUniqueName$managedFileName$version.zip"
        }
        #now we should have the final path
        $path = Join-Path $exportPath $solutionZipFileName

        Write-Verbose "Solution path: $path"

        #create the export request then set all the properties
        $exportRequest = New-Object Microsoft.Crm.Sdk.Messages.ExportSolutionRequest
        $exportRequest.ExportAutoNumberingSettings            =$ExportAutoNumberingSettings 
        $exportRequest.ExportCalendarSettings                 =$ExportCalendarSettings
        $exportRequest.ExportCustomizationSettings            =$ExportCustomizationSettings
        $exportRequest.ExportEmailTrackingSettings            =$ExportEmailTrackingSettings
        $exportRequest.ExportGeneralSettings                  =$ExportGeneralSettings
        $exportRequest.ExportIsvConfig                        =$ExportIsvConfig
        $exportRequest.ExportMarketingSettings                =$ExportMarketingSettings
        $exportRequest.ExportOutlookSynchronizationSettings   =$ExportOutlookSynchronizationSettings
        $exportRequest.ExportRelationshipRoles                =$ExportRelationshipRoles
        $exportRequest.Managed                                =$Managed
        $exportRequest.SolutionName                           =$solutionUniqueName
        $exportRequest.TargetVersion                          =$TargetVersion 

        if($conn.ConnectedOrgVersion.Major -ge 7)
        {
            $exportRequest.ExportSales = $ExportSales
        }

        Write-Verbose 'ExportSolutionRequests may take several minutes to complete execution.'

        $response = [Microsoft.Crm.Sdk.Messages.ExportSolutionResponse]($conn.ExecuteCrmOrganizationRequest($exportRequest))

        if($response -eq $null){
            if($conn.LastCrmException -eq ""){
                throw "The result was null, please double check the command"
            }
            else{
                throw LastCrmConnectorException($conn)
            }
        }

        Write-Verbose 'Using solution file to path: $path'

        [System.IO.File]::WriteAllBytes($path,$response.ExportSolutionFile)

        Write-Verbose "Successfully wrote file"
        $result = New-Object psObject

        Add-Member -InputObject $result -MemberType NoteProperty -Name "ExportSolutionResponse" -Value $response
        Add-Member -InputObject $result -MemberType NoteProperty -Name "SolutionPath" -Value $path

        return $result
    }
    catch
    {
        Write-Error $_.Exception
    }

    return $result
}

function Export-CrmSolutionTranslation{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml

    [CmdletBinding()]
    PARAM(
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn,
        [parameter(Mandatory=$true, Position=1)]
        [string]$SolutionName, 
        [parameter(Mandatory=$false)]
        [string]$TranslationFilePath,
        [parameter(Mandatory=$false)]
        [string]$TranslationZipFileName
    )    

    $conn = VerifyCrmConnectionParam $conn

    try
    {
        $solutionRecords = (Get-CrmRecords -conn $conn -EntityLogicalName solution -FilterAttribute uniquename -FilterOperator "like" -FilterValue $SolutionName -Fields publisherid,version )

        #if we can't find just one solution matching then ERROR
        if($solutionRecords.CrmRecords.Count -ne 1)
        {
            $friendlyName = $conn.ConnectedOrgFriendlyName.ToString()

            Write-Error "Solution with name `"$SolutionName`" in CRM Instance: `"$friendlyName`" not found!"
            break
        }
        #else PROCEED

        $version = $solutionRecords.CrmRecords[0].version 

        write-verbose "Solution found with version# $version"
       
        $exportPath = if($TranslationFilePath -ne ""){ $TranslationFilePath } else { Get-Location }
        
        #if a filename is not given, then we'll default one to CrmTranslations_[solutionname]_[version].zip
        if($TranslationZipFileName.Length -eq 0)
        {
            $version = $version.Replace('.','_')
            $translationZipFileName = "CrmTranslations_$SolutionName`_$version.zip"
        }

        #now we should have the final path
        $path = Join-Path $exportPath $translationZipFileName

        Write-Verbose "Solution path: $path"

        #create the export translation request then set all the properties
        $exportRequest = New-Object Microsoft.Crm.Sdk.Messages.ExportTranslationRequest
        $exportRequest.SolutionName = $SolutionName

        Write-Verbose 'ExportTranslationRequest may take several minutes to complete execution.'
        
        $response = [Microsoft.Crm.Sdk.Messages.ExportTranslationResponse]($conn.ExecuteCrmOrganizationRequest($exportRequest))

        [System.IO.File]::WriteAllBytes($path,$response.ExportTranslationFile)

        Write-Verbose "Successfully wrote file: $path"
        $result = New-Object psObject

        Add-Member -InputObject $result -MemberType NoteProperty -Name "ExportTranslationResponse" -Value $response
        Add-Member -InputObject $result -MemberType NoteProperty -Name "SolutionTranslationPath" -Value $path

        return $result
    }
    catch
    {
        Write-Error $_.Exception
    }

    return $result
}

function Get-CrmAllLanguagePacks{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml

    [CmdletBinding()]
    PARAM( 
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn
    )

    $conn = VerifyCrmConnectionParam $conn  

    $request = New-Object Microsoft.Crm.Sdk.Messages.RetrieveAvailableLanguagesRequest

    try
    {
        $response = $conn.ExecuteCrmOrganizationRequest($request, $null)
    }
    catch
    {
        throw LastCrmConnectorException($conn)
    }    

    return $response.LocaleIds
}

function Get-CrmEntityRecordCount{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml

    [CmdletBinding()]
    PARAM(
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn,
        [parameter(Mandatory=$true, Position=1)]
        [string]$EntityLogicalName
    )

    $conn = VerifyCrmConnectionParam $conn
    
    $count = 0
    $query = New-Object -TypeName 'Microsoft.Xrm.Sdk.Query.QueryExpression'
    $pageInfo = New-Object -TypeName 'Microsoft.Xrm.Sdk.Query.PagingInfo'
    $query.EntityName = $EntityLogicalName
    $pageInfo.Count = 5000
    $pageInfo.PageNumber = 1
    $pageInfo.PagingCookie = $null
    $query.PageInfo = $pageInfo
    
    while($True)
    {
        $request = New-Object -TypeName 'Microsoft.Xrm.Sdk.Messages.RetrieveMultipleRequest'
        $request.Query = $query
        try
        {
            $result = $conn.ExecuteCrmOrganizationRequest($request)
            if($result -eq $null)
            {
                throw LastCrmConnectorException($conn)
            }
        }
        catch
        {
            throw LastCrmConnectorException($conn)
        }
        
        $count += $result.EntityCollection.Entities.Count
        if($result.EntityCollection.MoreRecords)
        {
            $pageInfo.PageNumber += 1
            $pageInfo.PagingCookie = $result.EntityCollection.PagingCookie
        }
        else
        {
            break
        } 
    }
    
    return $count
}

function Get-CrmFailedWorkflows{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml

    [CmdletBinding()]
    PARAM(
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn,
        [parameter(Mandatory=$false, Position=2)]
        [int]$TopCount,
        [parameter(Mandatory=$false, Position=3)]
        [int]$PageNumber,
        [parameter(Mandatory=$false, Position=4)]
        [string]$PageCookie,
        [parameter(Mandatory=$false, Position=5)]
        [switch]$AllRows
    )
    
    $conn = VerifyCrmConnectionParam $conn
    
    $fetch = @"
<fetch version="1.0" output-format="xml-platform" mapping="logical" distinct="false" no-lock="true">
              <entity name="asyncoperation">
                <attribute name="asyncoperationid" />
                <attribute name="name" />
                <attribute name="regardingobjectid" />
                <attribute name="operationtype" />
                <attribute name="statuscode" />
                <attribute name="ownerid" />
                <attribute name="startedon" />
                <attribute name="statecode" />
                <attribute name="workflowstagename" />
                <attribute name="postponeuntil" />
                <attribute name="owningextensionid" />
                <attribute name="modifiedon" />
                <attribute name="modifiedonbehalfby" />
                <attribute name="modifiedby" />
                <attribute name="messagename" />
                <attribute name="message" />
                <attribute name="friendlymessage" />
                <attribute name="errorcode" />
                <attribute name="createdon" />
                <attribute name="createdonbehalfby" />
                <attribute name="createdby" />
                <attribute name="completedon" />
                <order attribute="startedon" descending="true" />
                <filter type="and">
                  <condition attribute="recurrencestarttime" operator="null" />
                  <condition attribute="message" operator="not-null" />
                </filter>
              </entity>
            </fetch>
"@

    
    if($AllRows){
        $results = Get-CrmRecordsByFetch -conn $conn -Fetch $fetch -TopCount $TopCount -PageNumber $PageNumber -PageCookie $PagingCookie -AllRows 
    }
    else{
        $results = Get-CrmRecordsByFetch -conn $conn -Fetch $fetch -TopCount $TopCount -PageNumber $PageNumber -PageCookie $PagingCookie 
    }

    if($results.CrmRecords.Count -eq 0)
    {
        Write-Warning 'No failed worklfows found.'
    }
    else
    {
        return $results
    }
}

function Get-CrmLicenseSummary{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml

    [CmdletBinding()]
    PARAM(
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn
    )

    $conn = VerifyCrmConnectionParam $conn

    $fetch = @"
    <fetch version="1.0" output-format="xml-platform" mapping="logical" distinct="false" no-lock="true">
        <entity name="systemuser">
            <attribute name="islicensed" />
            <attribute name="accessmode" />
            <attribute name="caltype" />
            <filter type='and'>
                <condition attribute='accessmode' operator='ne' value='3' />
                <condition attribute='domainname' operator='ne' value='' />
            </filter>
        </entity>
    </fetch>
"@
; 

    $users = Get-CrmRecordsByFetch -conn $conn -Fetch $fetch; 
    Write-Output 'IsLicensed:' ($users.CrmRecords | group islicensed | select count, name); 
    Write-Output 'AccessMode:' ($users.CrmRecords | group accessmode | select count, name); 
    Write-Output 'CalType:' ($users.CrmRecords | group accessmode | select count, name); 
}

function Get-CrmOrgDbOrgSettings{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml

    [CmdletBinding()]
    PARAM(
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn
    )

    $conn = VerifyCrmConnectionParam $conn
    
    $fetch = @"
    <fetch version="1.0" output-format="xml-platform" mapping="logical" distinct="false" no-lock="true">
      <entity name="organization">
        <attribute name="orgdborgsettings" />
      </entity>
    </fetch>
"@

    $result = Get-CrmRecordsByFetch -conn $conn -Fetch  $fetch
    $record = $result.CrmRecords[0]

    if($record.orgdborgsettings -eq $null)
    {
        Write-Warning 'No settings found.'
    }
    else
    {
        $xml = [xml]$record.orgdborgsettings
        return $xml.SelectSingleNode("/OrgSettings")
    }

}

function Get-CrmRecords{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml

    [CmdletBinding()]
    PARAM(
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn,
        [parameter(Mandatory=$true, Position=1)][alias("EntityName")]
        [string]$EntityLogicalName,
        [parameter(Mandatory=$false, Position=2)][alias("FieldName")]
        [string]$FilterAttribute,
        [parameter(Mandatory=$false, Position=3)][alias("Op")]
        [ValidateSet('eq','neq','ne','gt','ge','le','lt','like','not-like','in','not-in','between','not-between','null','not-null','yesterday','today','tomorrow','last-seven-days','next-seven-days','last-week','this-week','next-week','last-month','this-month','next-month','on','on-or-before','on-or-after','last-year','this-year','next-year','last-x-hours','next-x-hours','last-x-days','next-x-days','last-x-weeks','next-x-weeks','last-x-months','next-x-months','olderthan-x-months','olderthan-x-years','olderthan-x-weeks','olderthan-x-days','olderthan-x-hours','olderthan-x-minutes','last-x-years','next-x-years','eq-userid','ne-userid','eq-userteams','eq-useroruserteams','eq-useroruserhierarchy','eq-useroruserhierarchyandteams','eq-businessid','ne-businessid','eq-userlanguage','this-fiscal-year','this-fiscal-period','next-fiscal-year','next-fiscal-period','last-fiscal-year','last-fiscal-period','last-x-fiscal-years','last-x-fiscal-periods','next-x-fiscal-years','next-x-fiscal-periods','in-fiscal-year','in-fiscal-period','in-fiscal-period-and-year','in-or-before-fiscal-period-and-year','in-or-after-fiscal-period-and-year','begins-with','not-begin-with','ends-with','not-end-with','under','eq-or-under','not-under','above','eq-or-above')]
        [string]$FilterOperator,
        [parameter(Mandatory=$false, Position=4)][alias("Value", "FieldValue")]
        [string]$FilterValue,
        [parameter(Mandatory=$false, Position=5)]
        [string[]]$Fields, 
        [parameter(Mandatory=$false, Position=6)]
        [switch]$AllRows,
        [parameter(Mandatory=$false, Position=7)]
        [int]$TopCount
    )
    $conn = VerifyCrmConnectionParam $conn

    if($FilterOperator -and $FilterOperator.StartsWith("-"))
    {
        $FilterOperator = $FilterOperator.Remove(0, 1)
    }
    if( !($EntityLogicalName -cmatch "^[a-z_]*$") )
    {
        $EntityLogicalName = $EntityLogicalName.ToLower()
        Write-Verbose "EntityLogicalName contains uppercase which isn't possible in CRM, overwritting with ToLower() new value: $EntityLogicalName"
    }

    if( ($Fields -eq "*") -OR ($Fields -eq "%") )
    {
        Write-Verbose 'PERFORMANCE: All attributes were requested'
        $fetchAttributes = "<all-attributes/>"
    }
    elseif ($Fields)
    {
        foreach($Field in $Fields)
        {
            if($field -ne $null){
                $fetchAttributes += "<attribute name='{0}' />" -F $Field
            }
        }
    }
    else
    {
        #lookup the primary attribute
        $primaryAttribute = $conn.GetEntityMetadata($EntityLogicalName.ToLower()).PrimaryIdAttribute
        $fetchAttributes = "<attribute name='{0}' />" -F $primaryAttribute
    }

    #if any of the values are missing, but they're not *ALL* missing
    if(
        ($FilterAttribute -and !$FilterOperator) -or 
        (!$FilterAttribute -and $FilterOperator) -or
        ($FilterValue -and (!$FilterOperator -or !$FilterAttribute)) 
    ){
        #TODO: convert this to a parameter set to avoid this extra logic
        Write-Error "One of the `$FilterAttribute `$FilterOperator `$FilterValue parameters is empty, to query all records exclude all filter parameters."
        return
    }
    
    if($FilterAttribute -and $FilterOperator -and $FilterValue)
    {
        # Escape XML charactors
        $FilterValue = [System.Security.SecurityElement]::Escape($FilterValue)
        Write-Verbose "Using the supplied single filter of $FilterAttribute '$FilterOperator' $FilterValue"
        $fetch = 
@"
    <fetch version="1.0" output-format="xml-platform" mapping="logical" distinct="false" no-lock="true">
        <entity name="{0}">
            {1}
            <filter type='and'>
                <condition attribute='{2}' operator='{3}' value='{4}' />
            </filter>
        </entity>
    </fetch>
"@

    }
    elseif($FilterAttribute -and $FilterOperator){
         # Escape XML charactors
        $FilterValue = [System.Security.SecurityElement]::Escape($FilterValue)
        Write-Verbose "Using the supplied single filter of $FilterAttribute '$FilterOperator' and NO value"
        $fetch = 
@"
    <fetch version="1.0" output-format="xml-platform" mapping="logical" distinct="false" no-lock="true">
        <entity name="{0}">
            {1}
            <filter type='and'>
                <condition attribute='{2}' operator='{3}' />
            </filter>
        </entity>
    </fetch>
"@

    }
    else
    {
        Write-Verbose "PERFORMANCE: `$FilterAttribute `$FilterOperator `$FilterValue were not supplied, fetching all records with NO filter."
        $fetch = 
@"
    <fetch version="1.0" output-format="xml-platform" mapping="logical" distinct="false" no-lock="true">
        <entity name="{0}">
            {1}
        </entity>
    </fetch>
"@

    }
    $fetch = $fetch -F $EntityLogicalName, $fetchAttributes, $FilterAttribute, $FilterOperator, $FilterValue
    
    if($AllRows)
    {
        Write-Verbose "PERFORMANCE: All rows were requested instead of the first 5000"
        $results = Get-CrmRecordsByFetch -conn $conn -Fetch $fetch -AllRows
    }
    else
    {
        $results = Get-CrmRecordsByFetch -conn $conn -Fetch $fetch -TopCount $TopCount
    }
    
    return $results
}

function Get-CrmRecordsByViewName{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml

    [CmdletBinding()]
    PARAM(
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn,
        [parameter(Mandatory=$true, Position=1)]
        [string]$ViewName,
        [parameter(Mandatory=$false, Position=2)]
        [bool]$IsUserView, 
        [parameter(Mandatory=$false, Position=3)]
        [switch]$AllRows,
        [parameter(Mandatory=$false, Position=4)]
        [int]$TopCount
    )

    $conn = VerifyCrmConnectionParam $conn
    
    # Escape XML charactor
    $ViewName = [System.Security.SecurityElement]::Escape($ViewName)

    if($IsUserView)
    {
        $fetch = @"
    <fetch version="1.0" output-format="xml-platform" mapping="logical" distinct="false" no-lock="true">
        <entity name="userquery">
            <attribute name="fetchxml" />
            <filter type='and'>
                <condition attribute='name' operator='eq' value='{0}' />
            </filter>
        </entity>
    </fetch>
"@

    }
    else
    {
        $fetch = @"
    <fetch version="1.0" output-format="xml-platform" mapping="logical" distinct="false" no-lock="true">
        <entity name="savedquery">
            <attribute name="fetchxml" />
            <filter type='and'>
                <condition attribute='name' operator='eq' value='{0}' />
            </filter>
        </entity>
    </fetch>
"@

    }

    $fetch = $fetch -F $ViewName
    #get the views matching the search phrase
    $views = Get-CrmRecordsByFetch -conn $conn -Fetch $fetch

    if($views.CrmRecords.Count -eq 0)
    {
        Write-Warning "Couldn't find the view"
        break
    }
    if($AllRows)
    {
        $results = Get-CrmRecordsByFetch -conn $conn -Fetch $views.CrmRecords[0].fetchxml -AllRows
    }
    else
    {
        $results = Get-CrmRecordsByFetch -conn $conn -Fetch $views.CrmRecords[0].fetchxml -TopCount $TopCount
    }
    return $results
}

function Get-CrmRecordsCount{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml

    [CmdletBinding()]
    PARAM(
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn,
        [parameter(Mandatory=$true, Position=1)][alias("EntityName")]
        [string]$EntityLogicalName
    )
    $conn = VerifyCrmConnectionParam $conn        
    $fetch = 
@"
<fetch version="1.0" output-format="xml-platform" mapping="logical" distinct="false" no-lock="true">
    <entity name="{0}">
        <attribute name='{1}' />
    </entity>
</fetch>
"@

    
    if($EntityLogicalName -eq "usersettings")
    {
        $PrimaryKeyField = "systemuserid"
    }
    else
    {
        $PrimaryKeyField = "$EntityLogicalName`id"
    }
    $fetch = $fetch -F $EntityLogicalName, $PrimaryKeyField
    
    $results = Get-CrmRecordsByFetch -conn $conn -Fetch $fetch -AllRows
        
    return $results.Count
}

function Get-CrmSdkMessageProcessingStepsForPluginAssembly{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml

    [CmdletBinding()]
    PARAM(
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn,
        [parameter(Mandatory=$true, Position=1)]
        [string]$PluginAssemblyName,
        [parameter(Mandatory=$false, Position=2)]
        [switch]$OnlyCustomizable
    )

    $conn = VerifyCrmConnectionParam $conn
        
    if($OnlyCustomizable){ $isCustom = "<value>1</value>" } else { $isCustom = "<value>0</value><value>1</value>" }

    $fetch = @"
    <fetch version="1.0" output-format="xml-platform" mapping="logical" distinct="false" no-lock="true">
      <entity name="sdkmessageprocessingstep">
        <all-attributes/>
        <link-entity name="sdkmessagefilter" from="sdkmessagefilterid" to="sdkmessagefilterid" visible="false" link-type="outer" alias="a1">
          <attribute name="secondaryobjecttypecode" />
          <attribute name="primaryobjecttypecode" />
        </link-entity>
        <link-entity name="plugintype" from="plugintypeid" to="plugintypeid" alias="ab">
          <filter type="and">
            <condition attribute="assemblyname" operator="eq" value="{0}" />
          </filter>
        </link-entity>
        <filter type="and">
            <condition attribute="iscustomizable" operator="in">
                {1}
            </condition>
        </filter>
      </entity>
    </fetch>
"@
 -F $PluginAssemblyName, $isCustom
    
    $results = (Get-CrmRecordsByFetch -conn $conn -Fetch $fetch).CrmRecords
    
    return $results
}

function Get-CrmSiteMap{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml

    [CmdletBinding()]
    PARAM(
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn,
        [parameter(Mandatory=$false, ParameterSetName="ShowXml")]
        [switch]$SiteXml,
        [parameter(Mandatory=$false, ParameterSetName="ShowArea")]
        [switch]$Areas,
        [parameter(Mandatory=$false, ParameterSetName="ShowGroupOfArea")]
        [string]$GroupsOfArea,
        [parameter(Mandatory=$false, ParameterSetName="ShowSubAreaOfArea")]
        [string]$SubAreasOfArea
    )

    $conn = VerifyCrmConnectionParam $conn
  
    $fetch = @"
    <fetch version="1.0" output-format="xml-platform" mapping="logical" distinct="false" no-lock="true">
        <entity name="sitemap">
            <attribute name="sitemapxml" />
        </entity>
    </fetch>
"@


    $record = (Get-CrmRecordsByFetch -conn $conn -Fetch $fetch).CrmRecords[0]

    $sitemap  = [xml]$record.sitemapxml

    if($SiteXml.IsPresent)
    {
        return $sitemap.InnerXml
    }
    if($Areas.IsPresent)
    {
        return $sitemap.SelectNodes("/SiteMap").Area.Id
    }
    if($GroupsOfArea.Length -ne 0)
    {
        return $sitemap.SelectNodes("/SiteMap/Area[@Id='$GroupsOfArea']/Group").Id
    }
    if($SubAreasOfArea.Length -ne 0)
    {
        return $sitemap.SelectNodes("/SiteMap/Area[@Id='$SubAreasOfArea']/Group/SubArea").Id
    }    
}

function Get-CrmSystemSettings{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml

    [CmdletBinding()]
    PARAM(
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn,
        [parameter(Mandatory=$false)]
        [switch]$ShowDisplayName
    )

    $conn = VerifyCrmConnectionParam $conn
  
    $fetch = @"
    <fetch version="1.0" output-format="xml-platform" mapping="logical" distinct="false" no-lock="true">
        <entity name="organization">
            <all-attributes />
        </entity>
    </fetch>
"@

   
    $record = (Get-CrmRecordsByFetch -conn $conn -Fetch $fetch).CrmRecords[0]

    $attributes = Get-CrmEntityAttributes -conn $conn -EntityLogicalName organization

    $psobj = New-Object -TypeName System.Management.Automation.PSObject
        
    foreach($att in $record.original.GetEnumerator())
    {
        if(($att.Key.Contains("Property")) -or ($att.Key -eq "organizationid"))
        {
            continue
        }
        if($att.Key -eq "defaultemailsettings")
        {
            if($ShowDisplayName)
            {
                $name = ($attributes | where {$_.LogicalName -eq $att.Key}).Displayname.UserLocalizedLabel.Label + ":" +((Get-CrmEntityOptionSet mailbox incomingemaildeliverymethod).DisplayValue) 
            }
            else
            {
                $name = "defaultemailsettings:incomingemaildeliverymethod"
            }
            Add-Member -InputObject $psobj -MemberType NoteProperty -Name $name -Value ((Get-CrmEntityOptionSet mailbox incomingemaildeliverymethod).Items.DisplayLabel)[([xml]$att.Value).FirstChild.IncomingEmailDeliveryMethod]
            if($ShowDisplayName)
            {
                $name = ($attributes | where {$_.LogicalName -eq $att.Key}).Displayname.UserLocalizedLabel.Label + ":" +((Get-CrmEntityOptionSet mailbox outgoingemaildeliverymethod).DisplayValue) 
            }
            else
            {
                $name = "defaultemailsettings:outgoingemaildeliverymethod"
            }
            Add-Member -InputObject $psobj -MemberType NoteProperty -Name $name -Value ((Get-CrmEntityOptionSet mailbox outgoingemaildeliverymethod).Items.DisplayLabel)[([xml]$att.Value).FirstChild.OutgoingEmailDeliveryMethod]
            if($ShowDisplayName)
            {
                $name = ($attributes | where {$_.LogicalName -eq $att.Key}).Displayname.UserLocalizedLabel.Label + ":" +((Get-CrmEntityOptionSet mailbox actdeliverymethod).DisplayValue) 
            }
            else
            {
                $name = "defaultemailsettings:actdeliverymethod"
            }
            Add-Member -InputObject $psobj -MemberType NoteProperty -Name $name -Value ((Get-CrmEntityOptionSet mailbox actdeliverymethod).Items.DisplayLabel)[([xml]$att.Value).FirstChild.ACTDeliveryMethod]
            continue
        }
        
        if($ShowDisplayName)
        {
            $name = ($attributes | where {$_.LogicalName -eq $att.Key}).Displayname.UserLocalizedLabel.Label 
            if($name -eq $null)
            {
                $name = ($attributes | where {$_.LogicalName -eq $att.Key}).SchemaName
            }
        }
        else
        {
            $name = ($attributes | where {$_.LogicalName -eq $att.Key}).SchemaName
        }
        Add-Member -InputObject $psobj -MemberType NoteProperty -Name $name -Value $record.($att.Key) 
    }

    return $psobj
}

function Get-CrmTimeZones{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml

    [CmdletBinding()]
    PARAM(
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn
    )

    $conn = VerifyCrmConnectionParam $conn
    
    $fetch = @"
    <fetch version="1.0" output-format="xml-platform" mapping="logical" distinct="false" no-lock="true">
        <entity name="timezonedefinition">
            <attribute name="standardname" />
            <attribute name="timezonecode" />
            <attribute name="userinterfacename" />
            <order attribute="timezonecode" descending="false" />
        </entity>
    </fetch>
"@

    $results = Get-CrmRecordsByFetch -conn $conn -Fetch $fetch

    return $results.CrmRecords | select @{name="Timezone Name";expression={$_.userinterfacename}},@{name="TimeZone Code";expression={$_.timezonecode}}
}

function Get-CrmTraceAlerts{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml

    [CmdletBinding()]
    PARAM(
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn
    )

    $conn = VerifyCrmConnectionParam $conn
    
    $fetch = @"
    <fetch version="1.0" output-format="xml-platform" mapping="logical" distinct="false" no-lock="true">
        <entity name="tracelog">
            <attribute name="level" />
            <attribute name="regardingobjectid" />
            <attribute name="createdon" />
            <attribute name="modifiedon" />
            <attribute name="tracecode" />
            <attribute name="text" />
            <attribute name="modifiedby" />
            <attribute name="createdby" />
            <order attribute="modifiedon" descending="true" />
        </entity>
    </fetch>
"@

    $results = Get-CrmRecordsByFetch -conn $conn -Fetch $fetch

    if($results.CrmRecords.Count -eq 0)
    {
        Write-Warning 'No alert found.'
    }
    else
    {
        return $results
    }
}

function Get-CrmUserMailbox{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml

    [CmdletBinding()]
    PARAM(
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn,
        [parameter(Mandatory=$true, Position=1)]
        [string]$UserId
    )

    $conn = VerifyCrmConnectionParam $conn
    
    $fetch = @"
    <fetch version="1.0" output-format="xml-platform" mapping="logical" distinct="false" no-lock="true">
    <entity name="mailbox">
    <all-attributes />
    <filter type="and">
      <condition attribute="regardingobjectid" operator="eq" value="{$UserId}" />
    </filter>
  </entity>
</fetch>
"@


    $record = (Get-CrmRecordsByFetch -conn $conn -Fetch $fetch).CrmRecords
    switch ($record.count)
    {
        {$_ -eq 0} {Throw "The user $UserId has no mailbox."}
        {$_ -ge 2} {
                        foreach( $id in $record.mailboxid)
                        {
                            if(!$idString)
                            {
                                [string]$idString = $id
                            }
                            else
                            {
                                [string]$idString = "$idString,$id"
                            }
                        }
                        Throw "The user $UserId has more than one mailbox: $idString"
                    }
        Default {
                    return $record}
    }
    
}

function Get-CrmUserPrivileges{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml

    [CmdletBinding()]
    PARAM(
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn,
        [parameter(Mandatory=$true, Position=1)]
        [string]$UserId
    )

    $conn = VerifyCrmConnectionParam $conn
    
    # Get User Rolls including Team
    $roles = Get-CrmUserSecurityRoles -conn $conn -UserId $UserId -IncludeTeamRoles
    # Get User name
    $user = Get-CrmRecord -conn $conn -EntityLogicalName systemuser -Id $UserId -Fields fullname
    # Get all privilege records for PrivilegeName
    $privilegeRecords = (Get-CrmRecords -conn $conn -EntityLogicalName privilege -Fields name,privilegeid -WarningAction SilentlyContinue).CrmRecords
    # Create hash for performance reason
    $privileges = @{}
    $privilegeRecords | % {$privileges[$_.privilegeid] = $_.name} 

    # Create Result as hash for performance reason
    $results = @{}
    
    $isUserRoleInitialized = $false
    foreach($role in $roles | sort TeamName)
    {
        # Get all privileges for the role
        $request = New-Object Microsoft.Crm.Sdk.Messages.RetrieveRolePrivilegesRoleRequest
        
        try
        {
            $request.RoleId = $role.RoleId
            $rolePrivileges = ($conn.ExecuteCrmOrganizationRequest($request, $null)).RolePrivileges            
        }
        catch
        {
            throw LastCrmConnectorException($conn)
        }        
        
        foreach($rolePrivilege in $rolePrivileges)
        {
            # Create origin as "RoleName:Depth" format
            $origin = $role.RoleName + ":" + $rolePrivilege.Depth
            
            # If the role is assigned to a team, then add them separately.
            # For roles assign to the user, then accumulate them.
            if($isUserRoleInitialized -and $results.Contains($rolePrivilege.PrivilegeId))
            {
                $existingObj = $results[$rolePrivilege.PrivilegeId]
                                
                # Overwrite Depth only if it has higher privilege
                if([Microsoft.Crm.Sdk.Messages.PrivilegeDepth]::($rolePrivilege.Depth) -gt [Microsoft.Crm.Sdk.Messages.PrivilegeDepth]::($existingObj.Depth))
                {
                    $existingObj.Depth = $rolePrivilege.Depth
                }

                $existingObj.Origin += "," + $origin
            }
            else
            {       
                # Create new result object
                $psobj = New-Object -TypeName System.Management.Automation.PSObject
                if($role.TeamName -eq $null)
                {
                    $principalType = "User"
                    $principalName = $user.fullname
                    $key = $rolePrivilege.PrivilegeId
                }
                else
                {
                    $principalType = "Team"
                    $principalName = $role.TeamName
                    $key = $rolePrivilege.PrivilegeId.Guid + $origin
                }
                Add-Member -InputObject $psobj -MemberType NoteProperty -Name "Depth" -Value $rolePrivilege.Depth
                Add-Member -InputObject $psobj -MemberType NoteProperty -Name "PrivilegeId" -Value $rolePrivilege.PrivilegeId
                Add-Member -InputObject $psobj -MemberType NoteProperty -Name "PrivilegeName" -Value $privileges[($rolePrivilege.PrivilegeId)]
                Add-Member -InputObject $psobj -MemberType NoteProperty -Name "Origin" -Value $origin
                Add-Member -InputObject $psobj -MemberType NoteProperty -Name "PrincipalType" -Value $principalType
                Add-Member -InputObject $psobj -MemberType NoteProperty -Name "PrincipalName" -Value $principalName
                Add-Member -InputObject $psobj -MemberType NoteProperty -Name "BusinessUnitName" -Value $role.BusinessUnitName
                $results[$key] = $psobj
            }
        }

        if($role.TeamName -eq $null) {$isUserRoleInitialized = $true} 
    }
    
    return $results.Values | sort principalName, PrivilegeName
}

function Get-CrmUserSecurityRoles{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml

    [CmdletBinding()]
    PARAM(
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn,
        [parameter(Mandatory=$true, Position=1)]
        [string]$UserId,
        [parameter(Mandatory=$false)]
        [switch]$IncludeTeamRoles
    )

    $conn = VerifyCrmConnectionParam $conn
    
    $roles = New-Object System.Collections.Generic.List[PSObject]
    
    if($IncludeTeamRoles)
    {
        $fetch = @"
        <fetch version="1.0" output-format="xml-platform" mapping="logical" distinct="true" no-lock="true">
          <entity name="role">
            <attribute name="name"/>
            <attribute name="roleid" />
            <link-entity name="teamroles" from="roleid" to="roleid" visible="false" intersect="true">
              <link-entity name="team" from="teamid" to="teamid" alias="team">
              <attribute name="name"/>
              <attribute name="businessunitid"/>
                <link-entity name="teammembership" from="teamid" to="teamid" visible="false" intersect="true">
                  <link-entity name="systemuser" from="systemuserid" to="systemuserid" alias="af">
                    <filter type="and">
                      <condition attribute="systemuserid" operator="eq" value="{0}" />
                    </filter>
                  </link-entity>
                </link-entity>
              </link-entity>
            </link-entity>
          </entity>
        </fetch>
"@
 -F $UserId
        
        (Get-CrmRecordsByFetch -conn $conn -Fetch $fetch).CrmRecords | `
        select @{name="RoleId";expression={$_.roleid}}, @{name="RoleName";expression={$_.name}}, `
        @{name="TeamName";expression={$_.'team.name'}}, @{name="BusinessUnitName";expression={($_.'team.businessunitid').Name}} | `
        % {$roles.Add($_)}    
    }

    $fetch = @"
    <fetch version="1.0" output-format="xml-platform" mapping="logical" distinct="true" no-lock="true">
      <entity name="role">
        <attribute name="name" />
        <attribute name="roleid" />
        <order attribute="name" descending="false" />
        <link-entity name="systemuserroles" from="roleid" to="roleid" visible="false" intersect="true">
          <link-entity name="systemuser" from="systemuserid" to="systemuserid" alias="user">
          <attribute name="businessunitid"/>
            <filter type="and">
              <condition attribute="systemuserid" operator="eq" value="{0}" />
            </filter>
          </link-entity>
        </link-entity>
      </entity>
    </fetch>
"@
 -F $UserId
    
    (Get-CrmRecordsByFetch -conn $conn -Fetch $fetch).CrmRecords | `
    select @{name="RoleId";expression={$_.roleid}}, @{name="RoleName";expression={$_.name}}, `
    @{name="BusinessUnitName";expression={($_.'user.businessunitid').Name}}  | % { $roles.Add($_) }
    
    return $roles
}

function Get-CrmUserSettings{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml

    [CmdletBinding()]
    PARAM(
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn,
        [parameter(Mandatory=$true, Position=1)]
        [string]$UserId,
        [parameter(Mandatory=$true, Position=2)]
        [string[]]$Fields
    )

    $conn = VerifyCrmConnectionParam $conn
  
    return Get-CrmRecord -conn $conn -EntityLogicalName usersettings -Id $UserId -Fields $Fields
}

function Grant-CrmRecordAccess {
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml
    [OutputType([void])]
    [CmdletBinding()]
    PARAM(
        [parameter(Mandatory=$false, Position=0)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn,
        [parameter(Mandatory=$true, Position=1, ParameterSetName="CrmRecord", ValueFromPipeline=$true)]
        [PSObject[]]$CrmRecord,
        [parameter(Mandatory=$true, Position=1, ParameterSetName="NameWithId")]
        [string]$EntityLogicalName,
        [parameter(Mandatory=$true, Position=2, ParameterSetName="NameWithId")]
        [guid]$Id,
        [parameter(Mandatory=$true, Position=3)]
        [Microsoft.Xrm.Sdk.EntityReference]$Principal,
        [parameter(Mandatory=$true, Position=4)]
        [Microsoft.Crm.Sdk.Messages.AccessRights]$AccessMask
    )
    begin
    {
        $conn = VerifyCrmConnectionParam $conn

        if ($EntityLogicalName) {
            $CrmRecord += [PSCustomObject] @{
                logicalname = $EntityLogicalName
                "$($EntityLogicalName)id" = $Id
            }
        }
    }
    process
    {
        foreach ($record in $CrmRecord) {
            try {
                $request = [Microsoft.Crm.Sdk.Messages.GrantAccessRequest]::new()
                $request.Target = New-CrmEntityReference -EntityLogicalName $record.logicalname -Id $record.($record.logicalname + "id")
                $principalAccess = [Microsoft.Crm.Sdk.Messages.PrincipalAccess]::new()
                $principalAccess.Principal = $Principal
                $principalAccess.AccessMask = $AccessMask
                $request.PrincipalAccess = $principalAccess
                
                [Microsoft.Crm.Sdk.Messages.GrantAccessResponse]$conn.Execute($request) | Out-Null
            }
            catch {
                Write-Error $_
            }   
        }
    }
}

function Import-CrmSolutionTranslation{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml

    [CmdletBinding()]
    PARAM(
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn,
        [parameter(Mandatory=$true, Position=1)]
        [string]$TranslationFileName,
        [parameter(Mandatory=$false, Position=2)]
        [switch]$PublishChanges
    )    

    $conn = VerifyCrmConnectionParam $conn
       
    try
    {
        $importId = [guid]::NewGuid()
        $translationFile = [System.IO.File]::ReadAllBytes($TranslationFileName)

        #create the import translation request then set all the properties
        $importRequest = New-Object Microsoft.Crm.Sdk.Messages.ImportTranslationRequest
        $importRequest.TranslationFile = $translationFile
        $importRequest.ImportJobId = $importId
        
        Write-Verbose 'ImportTranslationRequest may take several minutes to complete execution.'
        $response = [Microsoft.Crm.Sdk.Messages.ImportTranslationResponse]($conn.ExecuteCrmOrganizationRequest($importRequest))
                
        Write-Verbose "Confirming the result"
        $xml = [xml](Get-CrmRecord -conn $conn -EntityLogicalName importjob -Id $importId -Fields data).data
        $importresult = $xml.importtranslations
        
        if($importresult.status -ne "Succeeded")
        {
            $importerrordetails = $importresult.errordetails
            Write-Verbose "Import result: $importerrordetails"
            throw $importerrordetails
        }
        else
        {            
            if($PublishChanges){
                Write-Verbose "Guid populated and user requested publish changes request..."
                Write-Verbose "Executing command: Publish-CrmAllCustomization, passing in the same connection"
            
                Publish-CrmAllCustomization -conn $conn
            }
            else{
                Write-Verbose "Import Complete don't forget to publish customizations."
            }
        }
    }
    catch
    {
        Write-Error $_.Exception
    }
}

function Invoke-CrmWhoAmI{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml

    [CmdletBinding()]
    PARAM( 
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn
    )
    $conn = VerifyCrmConnectionParam $conn

    $request = New-Object Microsoft.Crm.Sdk.Messages.WhoAmIRequest
    
    try
    {
        $result = $conn.ExecuteCrmOrganizationRequest($request, $null)
    }
    catch
    {
        throw LastCrmConnectorException($conn)
    }    

    return $result
}

function Invoke-CrmAction {
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml
    [OutputType([hashtable])]
    [OutputType([Microsoft.Xrm.Sdk.OrganizationResponse], ParameterSetName="Raw")]
    param (
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]
        $conn,

        [Parameter(
            Position=1,
            Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $Name,

        [Parameter(Position=2)]
        [hashtable]
        $Parameters,

        [Parameter(ValueFromPipeline, Position=3)]
        [ValidateNotNullOrEmpty()]
        [Microsoft.Xrm.Sdk.EntityReference]
        $Target,

        [Parameter(ParameterSetName="Raw")]
        [switch]
        $Raw
    )
    begin
    {
        $conn = VerifyCrmConnectionParam $conn
    }
    process
    {
        $request = new-object Microsoft.Xrm.Sdk.OrganizationRequest
        $request.RequestName = $Name
        if($Target) {
            $request.Parameters.Add("Target", $Target) 
        }

        if($Parameters) {
            foreach($parameter in $Parameters.GetEnumerator()) {
                $request.Parameters.Add($parameter.Name, $parameter.Value)
            }
        }

        try {
            $response = $conn.Execute($request)
        
            if($Raw) {
                Write-Output $response
            } elseif ($response.Results -and $response.Results.Count -gt 0) {
                $outputArguments = @{}
                foreach($outputArgument in $response.Results) {
                    $outputArguments.Add($outputArgument.Key, $outputArgument.Value)
                }
                Write-Output $outputArguments
            } else {
                Write-Output $null
            }
        }
        catch {
            Write-Error $_
        }
    }
}

function Publish-CrmCustomization{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml

 [CmdletBinding()]
    PARAM( 
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn,
        [parameter(Mandatory=$false)]
        [switch]$Entity,
        [parameter(Mandatory=$false)]
        [string[]]$EntityLogicalNames,
        [parameter(Mandatory=$false)]
        [switch]$Ribbon,
        [parameter(Mandatory=$false)]
        [switch]$SiteMap,
        [parameter(Mandatory=$false)]
        [switch]$Dashbord,
        [parameter(Mandatory=$false)]
        [guid[]]$DashbordIds,
        [parameter(Mandatory=$false)]
        [switch]$OptionSet,
        [parameter(Mandatory=$false)]
        [string[]]$OptionSetNames,
        [parameter(Mandatory=$false)]
        [switch]$WebResource,
        [parameter(Mandatory=$false)]
        [guid[]]$WebResourceIds
    )

    $conn = VerifyCrmConnectionParam $conn  

    $parameterXml = "<importexportxml>"

    if($Entity -and $EntityLogicalNames.Count -ne 0)
    {
        $parameterXml += "<entities>"
        foreach($entityLogicalName in $EntityLogicalNames)
        {
            $parameterXml += "<entity>" + $entityLogicalName + "</entity>"
        }
        $parameterXml += "</entities>"
    }
    if($Ribbon)
    {
        $parameterXml += "<ribbons><ribbon></ribbon></ribbons>"
    }
    if($Dashbord -and $DashbordIds.Count -ne 0)
    {
        $parameterXml += "<dashboards>"
        foreach($dashbordId in $DashbordIds)
        {
            $parameterXml += "<dashboard>{" + $dashbordId + "}</dashboard>"
        }
        $parameterXml += "</dashboards>"
    }
    if($OptionSet -and $OptionSetNames.Count -ne 0)
    {
        $parameterXml += "<optionsets>"
        foreach($optionSetName in $OptionSetNames)
        {
            $parameterXml += "<optionset>{" + $optionSetName + "}</optionset>"
        }
        $parameterXml += "</optionsets>"
    }
    if($SiteMap)
    {
        $parameterXml += "<sitemaps><sitemap></sitemap></sitemaps>"
    }
    if($WebResource -and $WebResourceIds.Count -ne 0)
    {
        $parameterXml += "<webresources>"
        foreach($webResourceId in $WebResourceIds)
        {
            $parameterXml += "<webresource>{" + $webResourceId + "}</webresource>"
        }
        $parameterXml += "</webresources>"
    }

    $parameterXml += "</importexportxml>"

    $request = New-Object Microsoft.Crm.Sdk.Messages.PublishXmlRequest
    $request.ParameterXml = $parameterXml
    try
    {
        $response = $conn.ExecuteCrmOrganizationRequest($request, $null)
        if($response.ResponseName -eq $null)
        {
            throw LastCrmConnectorException($conn)
        }
    }
    catch
    {
        throw LastCrmConnectorException($conn)
    }    
}

function Publish-CrmAllCustomization{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml

 [CmdletBinding()]
    PARAM( 
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn
    )

    $conn = VerifyCrmConnectionParam $conn  

    $request = New-Object Microsoft.Crm.Sdk.Messages.PublishAllXmlRequest
    
    try
    {
        $response = $conn.ExecuteCrmOrganizationRequest($request, $null)
    }
    catch
    {
        throw LastCrmConnectorException($conn)
    }    

    #return $response
}

function Remove-CrmSecurityRoleFromTeam{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml

    [CmdletBinding()]
    PARAM(
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn,
        [parameter(Mandatory=$true, Position=1, ParameterSetName="CrmRecord")]
        [PSObject]$TeamRecord,
        [parameter(Mandatory=$true, Position=2, ParameterSetName="CrmRecord")]
        [PSObject]$SecurityRoleRecord,
        [parameter(Mandatory=$true, Position=1, ParameterSetName="Id")]
        [string]$TeamId,
        [parameter(Mandatory=$true, Position=2, ParameterSetName="Id")]
        [string]$SecurityRoleId
    )

    $conn = VerifyCrmConnectionParam $conn

    if($PrincipalRecord -ne $null)
    {
        Remove-CrmRecordAssociation -conn $conn -CrmRecord1 $UserRecord -CrmRecord2 $SecurityRoleRecord -RelationshipName systemuserroles_association
    }
    else
    {
        Remove-CrmRecordAssociation -conn $conn -EntityLogicalName1 team -Id1 $TeamId -EntityLogicalName2 role -Id2 $SecurityRoleId -RelationshipName teamroles_association
    }
}

function Remove-CrmSecurityRoleFromUser{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml

    [CmdletBinding()]
    PARAM(
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn,
        [parameter(Mandatory=$true, Position=1, ParameterSetName="CrmRecord")]
        [PSObject]$UserRecord,
        [parameter(Mandatory=$true, Position=2, ParameterSetName="CrmRecord")]
        [PSObject]$SecurityRoleRecord,
        [parameter(Mandatory=$true, Position=1, ParameterSetName="Id")]
        [string]$UserId,
        [parameter(Mandatory=$true, Position=2, ParameterSetName="Id")]
        [string]$SecurityRoleId
    )

    $conn = VerifyCrmConnectionParam $conn

    if($PrincipalRecord -ne $null)
    {
        Remove-CrmRecordAssociation -conn $conn -CrmRecord1 $UserRecord -CrmRecord2 $SecurityRoleRecord -RelationshipName systemuserroles_association
    }
    else
    {
        Remove-CrmRecordAssociation -conn $conn -EntityLogicalName1 systemuser -Id1 $UserId -EntityLogicalName2 role -Id2 $SecurityRoleId -RelationshipName systemuserroles_association
    }
}

function Remove-CrmUserManager{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml
 [CmdletBinding()]
    PARAM( 
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn,
        [parameter(Mandatory=$true, Position=1)]
        [guid]$UserId
    )

    $conn = VerifyCrmConnectionParam $conn

    $request = New-Object 'Microsoft.Crm.Sdk.Messages.RemoveParentRequest'
    $target = New-CrmEntityReference systemuser $UserId
    $request.Target = $target
    
    try
    {
        $result = $conn.ExecuteCrmOrganizationRequest($request, $null)
        if($result -eq $null)
        {
            throw LastCrmConnectorException($conn)
        }
    }
    catch
    {
        throw LastCrmConnectorException($conn)
    } 
}

function Revoke-CrmEmailAddress{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml

    [CmdletBinding()]
    PARAM(
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn,
        [parameter(Mandatory=$true, Position=1, ParameterSetName="UserId")]
        [string]$UserId,
        [parameter(Mandatory=$true, Position=1, ParameterSetName="QueueId")]
        [string]$QueueId
    )

    $conn = VerifyCrmConnectionParam $conn

    if($UserId -ne "")
    {
        Set-CrmRecord -conn $conn -EntityLogicalName systemuser -Id $UserId -Fields @{"emailrouteraccessapproval"=(New-CrmOptionSetValue 3)}
    }
    else
    {
        Set-CrmRecord -conn $conn -EntityLogicalName queue -Id $QueueId -Fields @{"emailrouteraccessapproval"=(New-CrmOptionSetValue 3)}
    }
}

function Revoke-CrmRecordAccess {
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml
    [OutputType([void])]
    [CmdletBinding()]
    PARAM(
        [parameter(Mandatory=$false, Position=0)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn,
        [parameter(Mandatory=$true, Position=1, ParameterSetName="CrmRecord", ValueFromPipeline=$true)]
        [PSObject[]]$CrmRecord,
        [parameter(Mandatory=$true, Position=1, ParameterSetName="NameWithId")]
        [string]$EntityLogicalName,        
        [parameter(Mandatory=$true, Position=2, ParameterSetName="NameWithId")]
        [guid]$Id,
        [parameter(Mandatory=$true, Position=3)]
        [Microsoft.Xrm.Sdk.EntityReference]$Revokee
    )
    begin
    {
        $conn = VerifyCrmConnectionParam $conn

        if ($EntityLogicalName) {
            $CrmRecord += [PSCustomObject] @{
                logicalname = $EntityLogicalName
                "$($EntityLogicalName)id" = $Id
            }
        }
    }
    process
    {
        foreach ($record in $CrmRecord) {
            try {
                $request = [Microsoft.Crm.Sdk.Messages.RevokeAccessRequest]::new()
                $request.Target = New-CrmEntityReference -EntityLogicalName $record.logicalname -Id $record.($record.logicalname + "id")
                $request.Revokee = $Revokee
                
                [Microsoft.Crm.Sdk.Messages.RevokeAccessResponse]$conn.Execute($request) | Out-Null
            }
            catch {
                Write-Error $_
            }   
        }
    }
}

function Set-CrmSolutionVersionNumber {
    [CmdletBinding()]
    PARAM(
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn,
        [parameter(Mandatory=$true, Position=1)]
        [string]$SolutionName,
        [parameter(Mandatory=$true, Position=2)]
        [ValidatePattern('^(?:[\d]{1,}\.){1,3}[\d]{1,}$')]
        [string]$VersionNumber
    )

    $conn = VerifyCrmConnectionParam $conn

    $solutionRecords = (Get-CrmRecords -conn $conn -EntityLogicalName solution -FilterAttribute uniquename -FilterOperator "like" -FilterValue $SolutionName -Fields uniquename,version )
    #if we can't find just one solution matching then ERROR
    if($solutionRecords.CrmRecords.Count -ne 1)
    {
        $friendlyName = $conn.ConnectedOrgFriendlyName.ToString()
        throw "Solution with name `"$SolutionName`" in CRM Instance: `"$friendlyName`" not found!"
    }
    #else PROCEED
    
    $crmSolutionRecord = $solutionRecords.CrmRecords[0]
    $oldVersion = $crmSolutionRecord.version
    $crmSolutionRecord.version = $VersionNumber

    try
    {
        Write-Verbose "Updating $($crmSolutionRecord.uniquename) version to $VersionNumber"
        Set-CrmRecord -conn $conn -CrmRecord $crmSolutionRecord

        Write-Verbose "Successfully updated solution record"
        $result = New-Object psObject

        Add-Member -InputObject $result -MemberType NoteProperty -Name "PreviousVersionNumber" -Value $oldVersion
        Add-Member -InputObject $result -MemberType NoteProperty -Name "NewVersionNumber" -Value $VersionNumber
    }
    catch
    {
        throw LastCrmConnectorException($conn)
    }

    $result
}

function Set-CrmConnectionCallerId{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml
 [CmdletBinding()]
    PARAM( 
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn,
        [parameter(Mandatory=$true, position=1)][Alias("UserId")]
        [guid]$CallerId
    )

    $conn = VerifyCrmConnectionParam $conn

    # We may need to check if the CallerId exists and enabled.
    $conn.OrganizationServiceProxy.CallerId = $CallerId
}

function Set-CrmConnectionTimeout{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml
 [CmdletBinding()]
    PARAM( 
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn,
        [parameter(Mandatory=$false, position=1)]
        [Int64]$TimeoutInSeconds,
        [parameter(Mandatory=$false, position=1)]
        [switch]$SetDefault
    )

    $conn = VerifyCrmConnectionParam $conn
    #powershell 4.0+ is required for New-TimeSpan -Seconds $TimeoutInSeconds
    $newTimeout = New-Object System.TimeSpan -ArgumentList 0,0,120
    if(!$SetDefault){
        $newTimeout = New-Object System.TimeSpan -ArgumentList 0,0,$TimeoutInSeconds
    }
    if($conn.OrganizationServiceProxy -and $conn.OrganizationServiceProxy.Timeout){
        try{
            Write-Verbose "Updating Timeout on OrganizationServiceProxy"
            $conn.OrganizationServiceProxy.Timeout = $newTimeout
        }
        catch{
            Write-Verbose "Failed to set the timeout value"        
        }
    }
    if($conn.OrganizationWebProxyClient -and $conn.OrganizationWebProxyClient.ChannelFactory.Endpoint.Binding){
        try{
            Write-Verbose "Updating Timeouts on OrganizationWebProxyClient"
            $conn.OrganizationWebProxyClient.ChannelFactory.Endpoint.Binding.OpenTimeout = $newTimeout
            $conn.OrganizationWebProxyClient.ChannelFactory.Endpoint.Binding.CloseTimeout = $newTimeout
            $conn.OrganizationWebProxyClient.ChannelFactory.Endpoint.Binding.ReceiveTimeout = $newTimeout
            $conn.OrganizationWebProxyClient.ChannelFactory.Endpoint.Binding.SendTimeout = $newTimeout
        }
        catch{
            Write-Verbose "Failed to set the timeout values"
        }
    }
}

function Set-CrmSystemSettings {
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml
    [CmdletBinding()]
    PARAM(
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn,
        [parameter(Mandatory=$false)]
        [guid]$AcknowledgementTemplateId,
        [parameter(Mandatory=$false)]
        [int]$ACTDeliveryMethod,
        [parameter(Mandatory=$false)]
        [bool]$AllowAddressBookSyncs,
        [parameter(Mandatory=$false)]
        [bool]$AllowAutoResponseCreation,
        [parameter(Mandatory=$false)]
        [bool]$AllowAutoUnsubscribe,
        [parameter(Mandatory=$false)]
        [bool]$AllowAutoUnsubscribeAcknowledgement,
        [parameter(Mandatory=$false)]
        [bool]$AllowClientMessageBarAd,
        [parameter(Mandatory=$false)]
        [bool]$AllowEntityOnlyAudit,
        [parameter(Mandatory=$false)]
        [bool]$AllowMarketingEmailExecution,
        [parameter(Mandatory=$false)]
        [bool]$AllowOfflineScheduledSyncs,
        [parameter(Mandatory=$false)]
        [bool]$AllowOutlookScheduledSyncs,
        [parameter(Mandatory=$false)]
        [bool]$AllowUnresolvedPartiesOnEmailSend,
        [parameter(Mandatory=$false)]
        [bool]$AllowUserFormModePreference,
        [parameter(Mandatory=$false)]
        [bool]$AllowUsersSeeAppdownloadMessage,
        [parameter(Mandatory=$false)]
        [bool]$AllowWebExcelExport,
        [parameter(Mandatory=$false)]
        [string]$AMDesignator,
        [parameter(Mandatory=$false)]
        [bool]$AutoApplyDefaultonCaseCreate,
        [parameter(Mandatory=$false)]
        [bool]$AutoApplyDefaultonCaseUpdate,
        [parameter(Mandatory=$false)]
        [bool]$AutoApplySLA,
        [parameter(Mandatory=$false)]
        [string]$BingMapsApiKey,
        [parameter(Mandatory=$false)]
        [string]$BlockedAttachments,
        [parameter(Mandatory=$false)]
        [guid]$BusinessClosureCalendarId,
        [parameter(Mandatory=$false)]
        [string]$CampaignPrefix,
        [parameter(Mandatory=$false)]
        [bool]$CascadeStatusUpdate,
        [parameter(Mandatory=$false)]
        [string]$CasePrefix,
        [parameter(Mandatory=$false)]
        [string]$ContractPrefix,
        [parameter(Mandatory=$false)]
        [bool]$CortanaProactiveExperienceEnabled,
        [parameter(Mandatory=$false)]
        [bool]$CreateProductsWithoutParentInActiveState,
        [parameter(Mandatory=$false)]
        [int]$CurrencyDecimalPrecision,
        [parameter(Mandatory=$false)]
        [int]$CurrencyDisplayOption,
        [parameter(Mandatory=$false)]
        [int]$CurrentCampaignNumber,
        [parameter(Mandatory=$false)]
        [int]$CurrentCaseNumber,
        [parameter(Mandatory=$false)]
        [int]$CurrentContractNumber,
        [parameter(Mandatory=$false)]
        [int]$CurrentInvoiceNumber,
        [parameter(Mandatory=$false)]
        [int]$CurrentKbNumber,
        [parameter(Mandatory=$false)]
        [int]$CurrentOrderNumber,
        [parameter(Mandatory=$false)]
        [int]$CurrentQuoteNumber,
        [parameter(Mandatory=$false)]
        [ValidatePattern('\+{1}\d{1,}')]
        [string]$DefaultCountryCode,
        [parameter(Mandatory=$false)]
        [guid]$DefaultEmailServerProfileId,
        [parameter(Mandatory=$false)]
        [bool]$DisableSocialCare,
        [parameter(Mandatory=$false)]
        [bool]$DisplayNavigationTour,
        [parameter(Mandatory=$false)]
        [int]$EmailConnectionChannel, 
        [parameter(Mandatory=$false)]
        [int]$EmailCorrelationEnabled, 
        [parameter(Mandatory=$false)]
        [bool]$EnableBingMapsIntegration,
        [parameter(Mandatory=$false)]
        [bool]$EnableSmartMatching,
        [parameter(Mandatory=$false)]
        [int]$FullNameConventionCode,
        [parameter(Mandatory=$false)]
        [bool]$GenerateAlertsForErrors,
        [parameter(Mandatory=$false)]
        [bool]$GenerateAlertsForWarnings,
        [parameter(Mandatory=$false)]
        [bool]$GenerateAlertsForInformation,
        [parameter(Mandatory=$false)]
        [bool]$GlobalAppendUrlParametersEnabled,
        [parameter(Mandatory=$false)]
        [ValidatePattern('http(s)?://([\w-]+\.)+[\w-]+(/[\w- ./?%&=]*)?')]
        [string]$GlobalHelpUrl,
        [parameter(Mandatory=$false)]
        [bool]$GlobalHelpUrlEnabled,
        [parameter(Mandatory=$false)]
        [int]$HashDeltaSubjectCount,
        [parameter(Mandatory=$false)]
        [string]$HashFilterKeywords,
        [parameter(Mandatory=$false)]
        [int]$HashMaxCount,
        [parameter(Mandatory=$false)]
        [int]$HashMinAddressCount,
        [parameter(Mandatory=$false)]
        [bool]$IgnoreInternalEmail,
        [parameter(Mandatory=$false)]
        [int]$IncomingEmailDeliveryMethod,
        [parameter(Mandatory=$false)]
        [string]$InvoicePrefix,
        [parameter(Mandatory=$false)]
        [bool]$IsAutoSaveEnabled,
        [parameter(Mandatory=$false)]
        [bool]$IsDefaultCountryCodeCheckEnabled,
        [parameter(Mandatory=$false)]
        [bool]$IsDuplicateDetectionEnabled,
        [parameter(Mandatory=$false)]
        [bool]$IsDuplicateDetectionEnabledForImport,
        [parameter(Mandatory=$false)]
        [bool]$IsDuplicateDetectionEnabledForOfflineSync,
        [parameter(Mandatory=$false)]
        [bool]$IsDuplicateDetectionEnabledForOnlineCreateUpdate,
        [parameter(Mandatory=$false)]
        [bool]$IsFolderBasedTrackingEnabled,
        [parameter(Mandatory=$false)]
        [bool]$IsFullTextSearchEnabled,
        [parameter(Mandatory=$false)]
        [bool]$IsHierarchicalSecurityModelEnabled,
        [parameter(Mandatory=$false)]
        [bool]$IsPresenceEnabled,
        [parameter(Mandatory=$false)]
        [bool]$IsUserAccessAuditEnabled,
        [parameter(Mandatory=$false)]
        [string]$KbPrefix,
        [parameter(Mandatory=$false)]
        [int]$MaxAppointmentDurationDays,
        [parameter(Mandatory=$false)]
        [int]$MaxDepthForHierarchicalSecurityModel,
        [parameter(Mandatory=$false)]
        [int]$MaximumActiveBusinessProcessFlowsAllowedPerEntity,
        [parameter(Mandatory=$false)]
        [int]$MaximumDynamicPropertiesAllowed,
        [parameter(Mandatory=$false)]
        [int]$MaximumTrackingNumber,
        [parameter(Mandatory=$false)]
        [int]$MaxProductsInBundle,
        [parameter(Mandatory=$false)]
        [int]$MaxRecordsForExportToExcel,
        [parameter(Mandatory=$false)]
        [int]$MaxRecordsForLookupFilters,
        [parameter(Mandatory=$false)]
        [int]$MaxUploadFileSize,
        [parameter(Mandatory=$false)]
        [int]$MinAddressBookSyncInterval,
        [parameter(Mandatory=$false)]
        [int]$MinOfflineSyncInterval,
        [parameter(Mandatory=$false)]
        [int]$MinOutlookSyncInterval,
        [parameter(Mandatory=$false)]
        [bool]$NotifyMailboxOwnerOfEmailServerLevelAlerts,
        [parameter(Mandatory=$false)]
        [string]$OrderPrefix,
        [parameter(Mandatory=$false)]
        [int]$OutgoingEmailDeliveryMethod,
        [parameter(Mandatory=$false)]
        [ValidateSet(0,1,2)]
        [int]$PluginTraceLogSetting,
        [parameter(Mandatory=$false)]
        [ValidateSet(0,1,2,3,4)]
        [int]$PricingDecimalPrecision,
        [parameter(Mandatory=$false)]
        [bool]$QuickFindRecordLimitEnabled,
        [parameter(Mandatory=$false)]
        [string]$QuotePrefix,
        [parameter(Mandatory=$false)]
        [bool]$RequireApprovalForUserEmail,
        [parameter(Mandatory=$false)]
        [bool]$RequireApprovalForQueueEmail,
        [parameter(Mandatory=$false)]
        [bool]$ShareToPreviousOwnerOnAssign,
        [parameter(Mandatory=$false)]
        [string]$TrackingPrefix,
        [parameter(Mandatory=$false)]
        [int]$TrackingTokenIdBase,
        [parameter(Mandatory=$false)]
        [int]$TrackingTokenIdDigits,
        [parameter(Mandatory=$false)]
        [int]$UniqueSpecifierLength,
        [parameter(Mandatory=$false)]
        [bool]$UseLegacyRendering,
        [parameter(Mandatory=$false)]
        [bool]$UsePositionHierarchy,
        [parameter(Mandatory=$false)]
        [bool]$UseSkypeProtocol

    )

    $conn = VerifyCrmConnectionParam $conn
    
    $updateFields = @{}

    $attributesMetadata = Get-CrmEntityAttributes -conn $conn -EntityLogicalName organization
        
    $defaultEmailSettings = @{}        

    foreach($parameter in $MyInvocation.BoundParameters.GetEnumerator())
    {   
        $attributeMetadata = $attributesMetadata | ? {$_.SchemaName -eq $parameter.Key}

        if($parameter.Key -in ("IncomingEmailDeliveryMethod","OutgoingEmailDeliveryMethod","ACTDeliveryMethod"))
        {
            $defaultEmailSettings.Add($parameter.Key,$parameter.Value)
        }
        elseif($attributeMetadata -eq $null)
        {
            continue
        }
        elseif($attributeMetadata.AttributeType -eq "Picklist")
        {
            $updateFields.Add($parameter.Key.ToLower(), (New-CrmOptionSetValue $parameter.Value))
        }
        elseif($attributeMetadata.AttributeType -eq "Lookup")
        {
            $updateFields.Add($parameter.Key.ToLower(), (New-CrmEntityReference emailserverprofile $parameter.Value))
        }
        else
        {
            $updateFields.Add($parameter.Key.ToLower(), $parameter.Value)
        }
    }
    
    $fetch = @"
    <fetch version="1.0" output-format="xml-platform" mapping="logical" distinct="false" no-lock="true">
        <entity name="organization">
            <attribute name="organizationid" />
            <attribute name="defaultemailsettings" />
        </entity>
    </fetch>
"@


    $systemSettings = (Get-CrmRecordsByFetch -conn $conn -Fetch $fetch).CrmRecords[0]
    $recordid = $systemSettings.organizationid

    if($defaultEmailSettings.Count -ne 0)
    {        
        $emailSettings = [xml]$systemSettings.defaultemailsettings
        if($defaultEmailSettings.ContainsKey("IncomingEmailDeliveryMethod"))
        {
            $emailSettings.EmailSettings.IncomingEmailDeliveryMethod = [string]$defaultEmailSettings["IncomingEmailDeliveryMethod"]
        }
        if($defaultEmailSettings.ContainsKey("OutgoingEmailDeliveryMethod"))
        {
            $emailSettings.EmailSettings.OutgoingEmailDeliveryMethod = [string]$defaultEmailSettings["OutgoingEmailDeliveryMethod"]
        }
        if($defaultEmailSettings.ContainsKey("ACTDeliveryMethod"))
        {
            $emailSettings.EmailSettings.ACTDeliveryMethod = [string]$defaultEmailSettings["ACTDeliveryMethod"]
        }

        $updateFields.Add("defaultemailsettings",$emailSettings.OuterXml)
    }

    Set-CrmRecord -conn $conn -EntityLogicalName organization -Id $recordid -Fields $updateFields
}

function Set-CrmUserBusinessUnit{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml
 [CmdletBinding()]
    PARAM( 
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn,
        [parameter(Mandatory=$true, Position=1)]
        [guid]$UserId,
        [parameter(Mandatory=$true, Position=2)]
        [guid]$BusinessUnitId,
        [parameter(Mandatory=$false, Position=3)]
        [guid]$ReassignUserId
    )

    $conn = VerifyCrmConnectionParam $conn

    # If ReassignUserId is not passed, then assign them to myself
    if($ReassignUserId -eq $null)  
    {  
        $ReassignUserId = $UserId  
    }  

    $ReassignPrincipal = New-CrmEntityReference -EntityLogicalName systemuser -Id $ReassignUserId

    $request = New-Object 'Microsoft.Crm.Sdk.Messages.SetBusinessSystemUserRequest'
    $request.BusinessId = $BusinessUnitId
    $request.UserId = $UserId
    $request.ReassignPrincipal = $ReassignPrincipal

    try
    {
        $result = $conn.ExecuteCrmOrganizationRequest($request, $null)
        if($result -eq $null)
        {
            throw LastCrmConnectorException($conn)
        }
    }
    catch
    {
        throw LastCrmConnectorException($conn)
    }
}

function Set-CrmUserMailbox {
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml
    [CmdletBinding()]
    PARAM(
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn,
        [parameter(Mandatory=$true, Position=1)]
        [string]$UserId,
        [parameter(Mandatory=$false)]
        [string]$EmailAddress,
        [parameter(Mandatory=$false, ParameterSetName="Custom")]        
        [guid]$EmailServerProfile,
        [parameter(Mandatory=$false, ParameterSetName="Custom")]
        [int]$IncomingEmailDeliveryMethod,
        [parameter(Mandatory=$false, ParameterSetName="Custom")]
        [int]$OutgoingEmailDeliveryMethod,
        [parameter(Mandatory=$false, ParameterSetName="Custom")]
        [int]$ACTDeliveryMethod,
        [parameter(Mandatory=$false, ParameterSetName="Default")]
        [switch]$ApplyDefaultEmailSettings,
        [parameter(Mandatory=$false, ParameterSetName="Status")]
        [string]$StateCode,
        [parameter(Mandatory=$false, ParameterSetName="Status")]
        [string]$StatusCode,
        [parameter(Mandatory=$false, ParameterSetName="Status")]
        [switch]$ScheduleTest,
        [parameter(Mandatory=$false, ParameterSetName="Status")]
        [switch]$MarkedAsPrimaryForExchangeSync,
        [parameter(Mandatory=$false, ParameterSetName="Status")]
        [switch]$ApproveEmail
    )
    $conn = VerifyCrmConnectionParam $conn

    $fetch = @"
<fetch version="1.0" output-format="xml-platform" mapping="logical" distinct="false" no-lock="true">
  <entity name="mailbox">
    <attribute name="mailboxid" />
    <filter type="and">
      <condition attribute="regardingobjectid" operator="eq" value="{$UserId}" />
    </filter>
  </entity>
</fetch>
"@


    $Id = (Get-CrmRecordsByFetch -conn $conn -Fetch $fetch).CrmRecords[0].MailboxId

    $updateFields = @{}
    if($ApplyDefaultEmailSettings)
    {
        $fetch = @"
        <fetch version="1.0" output-format="xml-platform" mapping="logical" distinct="false" no-lock="true">
            <entity name="organization">
                <attribute name="defaultemailserverprofileid" />
                <attribute name="defaultemailsettings" />
            </entity>
        </fetch>
"@

        $record = (Get-CrmRecordsByFetch -conn $conn -Fetch $fetch).CrmRecords[0]
        $updateFields.Add("emailserverprofile", $record.defaultemailserverprofileid_Property.Value)
        $xml = [xml]$record.defaultemailsettings
        $updateFields.Add("incomingemaildeliverymethod", (New-CrmOptionSetValue $xml.ChildNodes.IncomingEmailDeliveryMethod))
        $updateFields.Add("outgoingemaildeliverymethod", (New-CrmOptionSetValue $xml.ChildNodes.OutgoingEmailDeliveryMethod))
        $updateFields.Add("actdeliverymethod", (New-CrmOptionSetValue $xml.ChildNodes.ACTDeliveryMethod))
    }
    if($ScheduleTest)
    {
        $updateFields.Add("testemailconfigurationscheduled", $true)
    }
    if($MarkedAsPrimaryForExchangeSync)
    {
        $updateFields.Add("orgmarkedasprimaryforexchangesync", $true)
    }
    if($ApproveEmail)
    {
        Approve-CrmEmailAddress -conn $conn -UserId $UserId
    }
    foreach($parameter in $MyInvocation.BoundParameters.GetEnumerator())
    {   
        if($parameter.Key -in ("EmailServerProfile"))
        {
            $updateFields.Add($parameter.Key.ToLower(), (New-CrmEntityReference emailserverprofile $parameter.Value))
        }
        elseif($parameter.Key -in ("IncomingEmailDeliveryMethod","OutgoingEmailDeliveryMethod","ACTDeliveryMethod"))
        {
            $updateFields.Add($parameter.Key.ToLower(), (New-CrmOptionSetValue $parameter.Value))
        }
        elseif($parameter.Key -in ("StateCode","StatusCode"))
        {
            Set-CrmRecordState -conn $conn -EntityLogicalName mailbox -Id $Id -StateCode $StateCode -StatusCode $StatusCode
        }
        elseif($parameter.Key -in ("EmailAddress"))
        {
            $updateFields.Add($parameter.Key.ToLower(), $parameter.Value)
        }
    }

    Set-CrmRecord -conn $conn -EntityLogicalName mailbox -Id $Id -Fields $updateFields
}

function Set-CrmQueueMailbox {
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml
    [CmdletBinding()]
    PARAM(
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn,
        [parameter(Mandatory=$true, Position=1)]
        [string]$QueueId,
        [parameter(Mandatory=$false)]
        [ValidatePattern('/^([a-zA-Z0-9])+([a-zA-Z0-9\._-])*@([a-zA-Z0-9_-])+([a-zA-Z0-9\._-]+)+$/')]
        [string]$EmailAddress,
        [parameter(Mandatory=$false, ParameterSetName="Custom")]        
        [guid]$EmailServerProfile,
        [parameter(Mandatory=$false, ParameterSetName="Custom")]
        [int]$IncomingEmailDeliveryMethod,
        [parameter(Mandatory=$false, ParameterSetName="Custom")]
        [int]$OutgoingEmailDeliveryMethod,
        [parameter(Mandatory=$false, ParameterSetName="Default")]
        [switch]$ApplyDefaultEmailSettings,
        [parameter(Mandatory=$false, ParameterSetName="Status")]
        [string]$StateCode,
        [parameter(Mandatory=$false, ParameterSetName="Status")]
        [string]$StatusCode,
        [parameter(Mandatory=$false, ParameterSetName="Status")]
        [switch]$ScheduleTest,
        [parameter(Mandatory=$false, ParameterSetName="Status")]
        [switch]$MarkedAsPrimaryForExchangeSync,
        [parameter(Mandatory=$false, ParameterSetName="Status")]
        [switch]$ApproveEmail
    )
    $conn = VerifyCrmConnectionParam $conn
    $fetch = @"
    <fetch version="1.0" output-format="xml-platform" mapping="logical" distinct="false" no-lock="true">
      <entity name="mailbox">
        <attribute name="mailboxid" />
        <filter type="and">
          <condition attribute="regardingobjectid" operator="eq" value="{$QueueId}" />
        </filter>
      </entity>
    </fetch>
"@


    $Id = (Get-CrmRecordsByFetch -conn $conn -Fetch $fetch).CrmRecords[0].MailboxId
    
    $updateFields = @{}
    if($ApplyDefaultEmailSettings)
    {
        $fetch = @"
        <fetch version="1.0" output-format="xml-platform" mapping="logical" distinct="false" no-lock="true">
            <entity name="organization">
                <attribute name="defaultemailserverprofileid" />
                <attribute name="defaultemailsettings" />
            </entity>
        </fetch>
"@

        $record = (Get-CrmRecordsByFetch -conn $conn -Fetch $fetch).CrmRecords[0]
        $updateFields.Add("emailserverprofile", $record.defaultemailserverprofileid_Property.Value)
        $xml = [xml]$record.defaultemailsettings
        $updateFields.Add("incomingemaildeliverymethod", (New-CrmOptionSetValue $xml.ChildNodes.IncomingEmailDeliveryMethod))
        $updateFields.Add("outgoingemaildeliverymethod", (New-CrmOptionSetValue $xml.ChildNodes.OutgoingEmailDeliveryMethod))
    }
    if($ScheduleTest)
    {
        $updateFields.Add("testemailconfigurationscheduled", $true)
    }
    if($MarkedAsPrimaryForExchangeSync)
    {
        $updateFields.Add("orgmarkedasprimaryforexchangesync", $true)
    }
    if($ApproveEmail)
    {
        Approve-CrmEmailAddress -conn $conn -QueueId $QueueId
    }
    
    foreach($parameter in $MyInvocation.BoundParameters.GetEnumerator())
    {   
        if($parameter.Key -in ("EmailServerProfile"))
        {
            $updateFields.Add($parameter.Key.ToLower(), (New-CrmEntityReference emailserverprofile $parameter.Value))
        }
        elseif($parameter.Key -in ("IncomingEmailDeliveryMethod","OutgoingEmailDeliveryMethod"))
        {
            $updateFields.Add($parameter.Key.ToLower(), (New-CrmOptionSetValue $parameter.Value))
        }
        elseif($parameter.Key -in ("StateCode","StatusCode"))
        {
            Set-CrmRecordState -conn $conn -EntityLogicalName mailbox -Id $Id -StateCode $StateCode -StatusCode $StatusCode
        }
        elseif($parameter.Key -in ("EmailAddress"))
        {
            $updateFields.Add($parameter.Key.ToLower(), $parameter.Value)
        }
    }
    
    Set-CrmRecord -conn $conn -EntityLogicalName mailbox -Id $Id -Fields $updateFields
}

function Set-CrmUserManager{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml

 [CmdletBinding()]
    PARAM( 
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn,
        [parameter(Mandatory=$true, Position=1)]
        [guid]$UserId,
        [parameter(Mandatory=$true, Position=2)]
        [guid]$ManagerId,
        [parameter(Mandatory=$true, Position=3)]
        [bool]$KeepChildUsers
    )

    $conn = VerifyCrmConnectionParam $conn

    $request = New-Object 'Microsoft.Crm.Sdk.Messages.SetParentSystemUserRequest'
    $request.ParentId = $ManagerId
    $request.UserId = $UserId
    $request.KeepChildUsers = $KeepChildUsers

    try
    {
        $result = $conn.ExecuteCrmOrganizationRequest($request, $null)
        if($result -eq $null)
        {
            throw LastCrmConnectorException($conn)
        }
    }
    catch
    {
        throw LastCrmConnectorException($conn)
    } 
}

function Set-CrmUserSettings{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml

    [CmdletBinding()]
    PARAM(
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn,
        [parameter(Mandatory=$true, Position=1)]
        [Alias("UserSettingsRecord")]
        [PSObject]$CrmRecord
    )

    $conn = VerifyCrmConnectionParam $conn
    
    try
    {
        $result = Set-CrmRecord -conn $conn -CrmRecord $CrmRecord
    }
    catch
    {
        throw
    }    
}

function Set-CrmRecordAccess {
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml
    [OutputType([void])]
    [CmdletBinding()]
    PARAM(
        [parameter(Mandatory=$false, Position=0)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn,
        [parameter(Mandatory=$true, Position=1, ParameterSetName="CrmRecord", ValueFromPipeline=$true)]
        [PSObject[]]$CrmRecord,
        [parameter(Mandatory=$true, Position=1, ParameterSetName="NameWithId")]
        [string]$EntityLogicalName,
        [parameter(Mandatory=$true, Position=2, ParameterSetName="NameWithId")]
        [guid]$Id,
        [parameter(Mandatory=$true, Position=3)]
        [Microsoft.Xrm.Sdk.EntityReference]$Principal,
        [parameter(Mandatory=$true, Position=4)]
        [Microsoft.Crm.Sdk.Messages.AccessRights]$AccessMask
    )
    begin
    {
        $conn = VerifyCrmConnectionParam $conn

        if ($EntityLogicalName) {
            $CrmRecord += [PSCustomObject] @{
                logicalname = $EntityLogicalName
                "$($EntityLogicalName)id" = $Id
            }
        }
    }
    process
    {
        foreach ($record in $CrmRecord) {
            try {
                $request = [Microsoft.Crm.Sdk.Messages.ModifyAccessRequest]::new()
                $request.Target = New-CrmEntityReference -EntityLogicalName $record.logicalname -Id $record.($record.logicalname + "id")
                $principalAccess = [Microsoft.Crm.Sdk.Messages.PrincipalAccess]::new()
                $principalAccess.Principal = $Principal
                $principalAccess.AccessMask = $AccessMask
                $request.PrincipalAccess = $principalAccess
                
                [Microsoft.Crm.Sdk.Messages.ModifyAccessResponse]$conn.Execute($request) | Out-Null
            }
            catch {
                Write-Error $_
            }   
        }
    }
}

### CRM Types ###
function New-CrmMoney{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml
 [CmdletBinding()]
    PARAM(        
        [parameter(Mandatory=$true, Position=0)]
        [double]$Value
    )

    $crmMoney = New-Object -TypeName Microsoft.Xrm.Sdk.Money
    $crmMoney.Value = $Value

    return $crmMoney
}

function New-CrmOptionSetValue{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml
 [CmdletBinding()]
    PARAM(        
        [parameter(Mandatory=$true, Position=0)]
        [int]$Value
    )

    $crmOptionSetValue = New-Object -TypeName Microsoft.Xrm.Sdk.OptionSetValue
    $crmOptionSetValue.Value = $Value

    return $crmOptionSetValue
}

function New-CrmEntityReference{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml
 [CmdletBinding()]
    PARAM(        
        [parameter(Mandatory=$true, Position=0)]
        [string]$EntityLogicalName,
        [parameter(Mandatory=$true, Position=1)]
        [guid]$Id
    )
    $crmEntityReference = New-Object -TypeName Microsoft.Xrm.Sdk.EntityReference
    $crmEntityReference.LogicalName = $EntityLogicalName
    $crmEntityReference.Id = $Id
    return $crmEntityReference
}

### Performance Tests ###
function Test-CrmViewPerformance{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml
    [CmdletBinding()]
    PARAM(
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn,
        [parameter(Mandatory=$true, Position=1, ParameterSetName="CrmRecord")][Alias("CrmRecord")]
        [PSObject]$View,
        [parameter(Mandatory=$true, Position=1, ParameterSetName="Id")][Alias("Id")]
        [guid]$ViewId,
        [parameter(Mandatory=$true, Position=1, ParameterSetName="Name")]
        [string]$ViewName,
        [parameter(Mandatory=$false)]
        [switch]$RunAsViewOwner,
        [parameter(Mandatory=$false)]
        [guid]$RunAs,
        [parameter(Mandatory=$false)]
        [switch]$IsUserView       
    )
    
    $conn = VerifyCrmConnectionParam $conn        
 
    if($IsUserView)
    { 
        Write-Verbose "querying userquery"
        $logicalName = "userquery"
        $fields = "name,fetchxml,layoutxml,returnedtypecode,ownerid".Split(",");      
    } 
    else
    {
        Write-Verbose "querying savedquery"
        $logicalName = "savedquery"
        $fields = "name,fetchxml,layoutxml,returnedtypecode".Split(",");
    }
    try
    {
        if($View -eq $null)
        {
            if($ViewId -ne $null)
            {        
                $View = Get-CrmRecord -conn $conn -EntityLogicalName $logicalName -Id $viewId -Fields $fields
            }
            elseif($viewName -ne "")
            {
                $views = Get-CrmRecords -conn $conn -EntityLogicalName $logicalName -FilterAttribute name -FilterOperator eq -FilterValue $viewName -Fields $fields
                if($views.CrmRecords.Count -eq 0) 
                { 
                    return 
                } 
                else 
                { 
                    $view = $views.CrmRecords[0]
                }
            }        
            else{
                throw "ViewID or ViewName is null, input a valid view name or View ID."
            }
        }
        # if the view has ownerid, then its User Defined View
        if($View.ownerid -ne $null)
        {
            if($RunAsViewOwner)
            {
                Set-CrmConnectionCallerId -conn $conn -CallerId $view.ownerid_property.Value.Id
            }
            elseif($RunAs -ne $null)
            {
                Set-CrmConnectionCallerId -conn $conn -CallerId $RunAs
            }
           
            # Get all records by using Fetch
            Test-CrmTimerStart
            $records = Get-CrmRecordsByFetch -conn $conn -Fetch $View.fetchxml -AllRows -ErrorAction SilentlyContinue -WarningAction SilentlyContinue
            $perf = Test-CrmTimerStop
            $owner = $View.ownerid
            $totalCount = $records.Count           
        }
        else
        {            
            if($RunAs -ne $null)
            {
                Set-CrmConnectionCallerId -conn $conn -CallerId $RunAs                
            }
            
            # Get all records by using Fetch
            Test-CrmTimerStart
            $records = Get-CrmRecordsByFetch -conn $conn -Fetch $View.fetchxml -AllRows -ErrorAction SilentlyContinue -WarningAction SilentlyContinue
            $perf = Test-CrmTimerStop
            $owner = "System"
            $totalCount = $records.Count
        }
        
        # Create result set
        $psobj = New-Object -TypeName System.Management.Automation.PSObject
        Add-Member -InputObject $psobj -MemberType NoteProperty -Name "ViewName" -Value $View.name 
        Add-Member -InputObject $psobj -MemberType NoteProperty -Name "FetchXml" -Value $View.fetchxml 
        Add-Member -InputObject $psobj -MemberType NoteProperty -Name "Entity" -Value $View.returnedtypecode
        Add-Member -InputObject $psobj -MemberType NoteProperty -Name "Columns" -Value ([xml]$view.layoutxml).grid.row.cell.Count
        Add-Member -InputObject $psobj -MemberType NoteProperty -Name "LayoutXml" -Value $view.layoutxml
        Add-Member -InputObject $psobj -MemberType NoteProperty -Name "TotalRecords" -Value $totalCount
        Add-Member -InputObject $psobj -MemberType NoteProperty -Name "Owner" -Value $owner
        Add-Member -InputObject $psobj -MemberType NoteProperty -Name "Performance" -Value $perf
        
        #before returning always set connection caller id back to ourself:
        if($RunAs -or $RunAsViewOwner){
            Write-Verbose "Setting connection caller id back to current user"
            Set-CrmConnectionCallerId -conn $conn -CallerId $RunAs                
        }

        return $psobj
    }
    catch
    {
        throw
    }
}

function Test-CrmTimerStart{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml
    $script:crmtimer = New-Object -TypeName 'System.Diagnostics.Stopwatch'
    $script:crmtimer.Start()
}

function Test-CrmTimerStop{
# .ExternalHelp Microsoft.Xrm.Data.PowerShell.Help.xml
    $crmtimerobj = Get-Variable crmtimer -Scope Script
    if($crmtimerobj.Value -ne $null)
    {
        $crmtimer = $crmtimerobj.Value
        $crmtimer.Stop()
        $perf = "The operation took " + $crmtimer.Elapsed.ToString()
        Remove-Variable crmtimer  -Scope Script
        return $perf
    }
}

### Internal Helpers
function parseRecordsPage {
    PARAM( 
        [parameter(Mandatory=$true)]
        [object]$records,
        [parameter(Mandatory=$true)]
        [string] $logicalname,
        [parameter(Mandatory=$true)]
        [xml] $xml
    )
    #$elapsed = [System.Diagnostics.Stopwatch]::StartNew()
    $recordslist = New-Object 'System.Collections.Generic.List[System.Management.Automation.PSObject]'
        #future: create expected attributes
    <#
    $expectedAttributes = $xml.GetElementsByTagName('attribute')
    $template = @{}
    foreach($att in $expectedAttributes){
        if($att.ParentNode.HasAttribute('alias')){
            $attName = $att.ParentNode.GetAttribute('alias') + "." + $att.name
        }
        else{
            $attName = $att.name
        }
        $template.Add($attName, $null)
    }
    #>

    foreach($record in $records.Values){   
        $record.Add("original",$record)
        $record.Add("logicalname",$logicalname)
        if($record.ContainsKey("ReturnProperty_Id ") -eq $true){
            $record.Add("ReturnProperty_Id",$record.'ReturnProperty_Id ')|Out-Null
            $record.Remove("ReturnProperty_Id ")|Out-Null
        }
        #add entityReferences values as values
        ForEach($attribute in ($record.Keys|Select)){
            if($attribute.EndsWith("_Property")){
                if($record[$attribute].Value -is [Microsoft.Xrm.Sdk.AliasedValue]){
                    #if aliased value BUT if it's an EntityRef... then ignore it
                    if($record[$attribute].Value.Value -isnot [Microsoft.Xrm.Sdk.EntityReference]){
                        $attName = $attribute.Replace("_Property","")
                        $record[$attName] = $record[$attribute].Value.Value
                    }
                                
                }
                if($record[$attribute].Value -is [Microsoft.Xrm.Sdk.EntityReference]){
                    $attName = $attribute.Replace("_Property","")
                    $record[$attName] = $record[$attribute].Value.Name
                }
            }
        }
        #future: consider merging the output record with the template set of expected attributes - however, this has negative performance implications
        <#
        ForEach ($Key in $template.Keys) {
            if(!$record.ContainsKey($Key)) {
                $record.Add($Key, $null)
            }
        }
        #>

        #convert hashtable to a ps object
        $psobj = New-Object -TypeName PSObject -Property $record

        #adding Dynamic EntityReference
        if($psobj."ReturnProperty_Id" -ne $null -and $psobj."ReturnProperty_EntityName" -ne $null){
            Add-Member -InputObject $psobj -MemberType NoteProperty -Name "EntityReference" -Value (New-CrmEntityReference -EntityLogicalName $psobj."ReturnProperty_EntityName" -Id $psobj."ReturnProperty_Id")
        }
        $recordslist.Add($psobj)
    }
   return $recordslist
}
function Coalesce {
    foreach($i in $args){
        if($i -ne $null){
            return $i
        }
    }
}
function VerifyCrmConnectionParam {
    [CmdletBinding()]
    PARAM( 
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn
    )

    if($conn -eq $null)
    {
        $connobj = Get-Variable conn -Scope global -ErrorAction SilentlyContinue
        if($connobj.Value -eq $null)
        {
            throw 'A connection to CRM is required, use Get-CrmConnection or one of the other connection functions to connect.'
        }
        else
        {
            $conn = $connobj.Value
        }
    }
    return $conn
}
function MapFieldTypeByFieldValue {
    PARAM(
        [Parameter(Mandatory=$true)]
        [object]$Value
    )

    $valueTypeToCrmTypeMapping = @{
        "Boolean" = [Microsoft.Xrm.Tooling.Connector.CrmFieldType]::CrmBoolean;
        "DateTime" = [Microsoft.Xrm.Tooling.Connector.CrmFieldType]::CrmDateTime;
        "Decimal" = [Microsoft.Xrm.Tooling.Connector.CrmFieldType]::CrmDecimal;
        "Single" = [Microsoft.Xrm.Tooling.Connector.CrmFieldType]::CrmFloat;
        "Money" = [Microsoft.Xrm.Tooling.Connector.CrmFieldType]::Raw;
        "Int32" = [Microsoft.Xrm.Tooling.Connector.CrmFieldType]::CrmNumber;
        "EntityReference" = [Microsoft.Xrm.Tooling.Connector.CrmFieldType]::Raw;
        "OptionSetValue" = [Microsoft.Xrm.Tooling.Connector.CrmFieldType]::Raw;
        "String" = [Microsoft.Xrm.Tooling.Connector.CrmFieldType]::String;
        "Guid" =  [Microsoft.Xrm.Tooling.Connector.CrmFieldType]::UniqueIdentifier;
    }

    # default is RAW
    $crmDataType = [Microsoft.Xrm.Tooling.Connector.CrmFieldType]::Raw

    if($Value -ne $null) {

        $valueType = $Value.GetType().Name
        
        if($valueTypeToCrmTypeMapping.ContainsKey($valueType)) {
            $crmDataType = $valueTypeToCrmTypeMapping[$valueType]
        }   
    }

    return $crmDatatype
}
function GuessPrimaryKeyField() {
    PARAM(
        [Parameter(Mandatory=$true)]
        [object]$EntityLogicalName
    )

    $standardActivityEntities = @(
        "opportunityclose",
        "socialactivity",
        "campaignresponse",
        "letter","orderclose",
        "appointment",
        "recurringappointmentmaster",
        "fax",
        "email",
        "activitypointer",
        "incidentresolution",
        "bulkoperation",
        "quoteclose",
        "task",
        "campaignactivity",
        "serviceappointment",
        "phonecall"
    )

    # Some Entity has different pattern for id name.
    if($EntityLogicalName -eq "usersettings")
    {
        $primaryKeyField = "systemuserid"
    }
    elseif($EntityLogicalName -eq "systemform")
    {
        $primaryKeyField = "formid"
    }
    elseif($EntityLogicalName -in $standardActivityEntities)
    {
        $primaryKeyField = "activityid"
    }
    else 
    {
        # default
        $primaryKeyField = $EntityLogicalName + "id"
    }
    
    $primaryKeyField
}
function LastCrmConnectorException {
    [CmdletBinding()]
    PARAM( 
        [parameter(Mandatory=$true)]
        [Microsoft.Xrm.Tooling.Connector.CrmServiceClient]$conn
    )

    return (Coalesce $conn.LastCrmError $conn.LastCrmException) 
}
function AddTls12Support {
    #by default PowerShell will show Ssl3, Tls - since SSL3 is not desirable we will drop it and use Tls + Tls12
    [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls -bor [System.Net.SecurityProtocolType]::Tls12
}
## Taken from CRM SDK sample code
## https://msdn.microsoft.com/en-us/library/microsoft.crm.sdk.messages.retrieveentityribbonresponse.compressedentityxml.aspx
function UnzipCrmRibbon {
    PARAM( 
        [parameter(Mandatory=$true)]
        [Byte[]]$Data
    )

    $memStream = New-Object System.IO.MemoryStream

    $memStream.Write($Data, 0, $Data.Length)
    $package = [System.IO.Packaging.ZipPackage]::Open($memStream, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read)
    $part = $package.GetPart([System.Uri]::new('/RibbonXml.xml', [System.UriKind]::Relative))

    try
    {
        $strm = $part.GetStream()
        $reader = [System.Xml.XmlReader]::Create($strm)

        $xmlDoc = New-Object System.Xml.XmlDocument
        $xmlDoc.Load($reader)

        return $xmlDoc
    }
    finally
    {
        if ($strm -ne $null)
        {
            $strm.Dispose()
            $strm = $null
        }
        if ($reader -ne $null)
        {
            $reader.Dispose()
            $reader = $null
        }
    }
}