Public/Solution.ps1


#ImportSolutionToCrmAsync
function Import-CrmSolutionAsync {
# .ExternalHelp Campgemini.Xrm.Data.PowerShell.Help.xml
    [CmdletBinding()]
    PARAM(
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Sdk.IOrganizationService]$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,
        [parameter(Mandatory=$false, Position=9)]
        [int64]$PollingDelayInSeconds = 5
    )
    $conn = VerifyCrmConnectionParam -conn $conn -pipelineValue ($PSBoundParameters.ContainsKey('conn'))
    $importId = [guid]::Empty
    $asyncResponse = $null
    try
    {
        if (!(Test-Path $SolutionFilePath)) 
        {
            throw [System.IO.FileNotFoundException] "$SolutionFilePath not found."
        }
        
        Write-Verbose  "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 "Importing solution. This process can take a while..."
        try
        {
            $asyncResponse = ($conn.Execute($asyncRequest, "AsyncImportRequest") -as [Microsoft.Xrm.Sdk.Messages.ExecuteAsyncResponse]) 
            $importId = $asyncResponse.AsyncJobId
            
            Write-Verbose "Async Operation ID (asyncoperationid): $importId" 
            if($importId -eq $null -or $importId -eq [Guid]::Empty)
            {
                throw "Import request failed for Async Operation {$importId}"
            }
            #if the caller wants to get the ID and does NOT want to wait
            if($BlockUntilImportComplete -eq $false){
                return $asyncResponse; 
            }
        }
        catch
        {
            throw $_.Exception
        }

        $pollingStart = Get-Date
        $isProcessing = $true
        $secondsSpentPolling = 0
        $transientFailureCount = 0; 
        Write-Verbose "Solution import requested, waiting on completion of Async Operation {$importId}..."

        try{
            while(($isProcessing -and $secondsSpentPolling -lt $MaxWaitTimeInSeconds) -or ($isProcessing -and $MaxWaitTimeInSeconds -eq -1)){
                #delay
                Start-Sleep -Seconds $PollingDelayInSeconds

                #check the import job for success/fail/inProgress
                try{
                    $import = Get-CrmRecord -conn $conn -EntityLogicalName asyncoperation -Id $importId -Fields statuscode,completedon,friendlymessage
                } catch {
                    $transientFailureCount++; 
                    Write-Verbose "Import Job status check did not succeed: $($_.Exception)"
                }

                $statuscode = $import.statuscode_Property.value.Value;
                
                #Check for import completion - https://msdn.microsoft.com/en-us/library/gg309288.aspx
                if($statuscode -lt 30){
                    $secondsSpentPolling = ([Int]((Get-Date) - $pollingStart).TotalSeconds)
                    Write-Verbose "Executing for $($secondsSPentPolling.ToString())/$MaxWaitTimeInSeconds seconds | Status: $($import.statuscode) ($statuscode)"
                }
                elseif($statuscode -eq 31 -or $statuscode -eq 32 ){
                    $isProcessing = $false
                    throw "Status: $($import.statuscode) ($statuscode) | Operation has been either cancelled or has failed for Async Operation {$importId}.`n$($import.friendlymessage)"
                    break; 
                }
                elseif($statuscode -eq 30){
                    $isProcessing = $false
                    Write-Verbose "Processing Completed at: $($import.completedon)"
                    if($PublishChanges){
                        Write-Verbose "SUCCESS: PublishChanges set, executing: Publish-CrmAllCustomization using the same connection."
                        Publish-CrmAllCustomization -conn $conn
                        return $import
                    }
                    else{
                        Write-Verbose "SUCCESS: Import Complete, don't forget to publish customizations."
                        return $import
                    }
                    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 Campgemini.Xrm.Data.PowerShell.Help.xml
    [CmdletBinding()]
    PARAM(
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Sdk.IOrganizationService]$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 $conn -pipelineValue ($PSBoundParameters.ContainsKey('conn'))
    $importId = [guid]::Empty
    try
    {
        if (!(Test-Path $SolutionFilePath)) 
        {
            throw [System.IO.FileNotFoundException] "$SolutionFilePath not found."
        }
        Write-Verbose "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 $_.Exception
        }
        $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()) 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
    }    
}

#MergeHoldingSolution
function Merge-CrmHoldingSolutionAsync {
    [CmdletBinding()]
    PARAM(
        [parameter(Mandatory=$true)]
        [Microsoft.Xrm.Sdk.IOrganizationService]$conn,
        [parameter(Mandatory=$true, Position=1)]
        [string]$CrmSolutionName,
        [parameter(Mandatory=$false, Position=2)]
        [int64]$MaxWaitTimeInSeconds,
        [parameter(Mandatory=$false, Position=3)]
        [switch]$BlockUntilImportComplete,
        [parameter(Mandatory=$false, Position=4)]
        [int64]$PollingDelayInSeconds = 5
    )

    $conn = VerifyCrmConnectionParam -conn $conn -pipelineValue ($PSBoundParameters.ContainsKey('conn'))

    $request = New-Object Microsoft.Crm.Sdk.Messages.DeleteAndPromoteRequest
    $request.UniqueName = $CrmSolutionName

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

    try
    {
        Write-Verbose "Applying async solution upgrade for solution '$CrmSolutionName'"
        $asyncResponse = ($conn.Execute($asyncRequest, "AsyncImportRequest") -as [Microsoft.Xrm.Sdk.Messages.ExecuteAsyncResponse]) 
        $asyncOperationId = $asyncResponse.AsyncJobId
        
        Write-Verbose "Async Operation ID (asyncoperationid): $asyncOperationId" 
        if(($asyncOperationId -eq $null) -or ($asyncOperationId -eq [Guid]::Empty))
        {
            throw "Async request failed!"
        }

        #if the caller wants to get the ID and does NOT want to wait
        if($BlockUntilImportComplete -eq $false){
            return $asyncResponse
        }
    }
    catch
    {
        throw $_.Exception
    }

    $pollingStart = Get-Date
    $isProcessing = $true
    $secondsSpentPolling = 0
    $transientFailureCount = 0
    Write-Verbose "Async Delete and Promote requested, waiting on completion..."

    try{
        while(($isProcessing -and $secondsSpentPolling -lt $MaxWaitTimeInSeconds) -or ($isProcessing -and $MaxWaitTimeInSeconds -eq -1)) {
            #delay
            Start-Sleep -Seconds $PollingDelayInSeconds

            #check the async job for success/fail/inProgress
            try{
                $asyncOperation = Get-CrmRecord -conn $conn -EntityLogicalName asyncoperation -Id $asyncOperationId -Fields statuscode,completedon,friendlymessage
            } catch {
                $transientFailureCount++; 
                Write-Verbose "Async job status check did not succeed: $($_.Exception)"
            }

            $statuscode = $asyncOperation.statuscode_Property.value.Value;
            
            #Check for import completion - https://msdn.microsoft.com/en-us/library/gg309288.aspx
            if($statuscode -lt 30){
                $secondsSpentPolling = ([Int]((Get-Date) - $pollingStart).TotalSeconds)
                Write-Verbose "Executing for $($secondsSPentPolling.ToString())/$TimeoutInSeconds seconds | Status: $($asyncOperation.statuscode) ($statuscode)"
            }
            elseif($statuscode -eq 31 -or $statuscode -eq 32 ){
                $isProcessing = $false
                throw "Status: $($asyncOperation.statuscode) ($statuscode) | Operation has been either cancelled or has failed.`n$($asyncOperation.friendlymessage)"
                break;
            }
            elseif($statuscode -eq 30){
                $isProcessing = $false
                Write-Verbose "Processing Completed at: $($asyncOperation.completedon)"
                Write-Verbose "SUCCESS: Delete and Promote successfully completed."
                return $asyncOperation
            }
        }
        
        #User provided timeout and exit function with an error
        if($secondsSpentPolling -gt $MaxWaitTimeInSeconds){
            Write-Warning "Delete and Promote request exited due to exceeding the maximum timeout of $MaxWaitTimeInSeconds. The import will continue in CRM asynchronously until it either succeeds or fails."
        }

        #at this point request appears to have succeeded
        return $asyncResponse; 
    } catch {
        throw "AsyncOperation with ID: $asyncOperationId has encountered an exception: $_"
    }
}

#InstallSampleDataToCrm
function Add-CrmSampleData{
# .ExternalHelp Campgemini.Xrm.Data.PowerShell.Help.xml
    [CmdletBinding()]
    PARAM(
        [parameter(Mandatory=$false, Position=0)]
        [Microsoft.Xrm.Sdk.IOrganizationService]$conn
    )
    $conn = VerifyCrmConnectionParam -conn $conn -pipelineValue ($PSBoundParameters.ContainsKey('conn')) 
    try
    {
        $result = $conn.InstallSampleDataToCrm()
        if($result -eq $null)
        {
            throw "Could not add sample data"
        }
    }
    catch
    {
        throw 
    }
    return $result
}

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

    $conn = VerifyCrmConnectionParam -conn $conn -pipelineValue ($PSBoundParameters.ContainsKey('conn')) 

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

    return $result
}

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

    $conn = VerifyCrmConnectionParam -conn $conn -pipelineValue ($PSBoundParameters.ContainsKey('conn'))  

    try
    {
        $result = $conn.PublishEntity($EntityLogicalName)
        if(!$result)
        {
            throw "Could not publish entity"
        }
    }
    catch
    {
        throw 
    }

    return $result
}

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

    $conn = VerifyCrmConnectionParam -conn $conn -pipelineValue ($PSBoundParameters.ContainsKey('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.Execute($req, $null)
        if(!$result)
        {
            throw "Could not publish theme"
        }
    }
    catch
    {
        throw 
    }

    return $result
}

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

    $conn = VerifyCrmConnectionParam -conn $conn -pipelineValue ($PSBoundParameters.ContainsKey('conn'))  

    if($EntityLogicalName -eq "")
    {
        $EntityLogicalName = $null
    }
    
    try
    {
        $result = $conn.ResetLocalMetadataCache($EntityLogicalName)
        if($result -eq $null)
        {
            throw "Could not remove metadata cache"
        }
    }
    catch
    {
        throw
    }    
}

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

    $conn = VerifyCrmConnectionParam -conn $conn -pipelineValue ($PSBoundParameters.ContainsKey('conn')) 

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

    return $result
}

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

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

    $conn = VerifyCrmConnectionParam -conn $conn -pipelineValue ($PSBoundParameters.ContainsKey('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.Execute($request)
        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 "Could not export application ribbon xml"
    }
    catch
    {
        throw $_.Exception
    }
}

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

    $conn = VerifyCrmConnectionParam -conn $conn -pipelineValue ($PSBoundParameters.ContainsKey('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.Execute($request)
        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 "Could not export entity ribbon xml"
    }
    catch
    {
        throw 
    }
}

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

    [CmdletBinding()]
    PARAM(
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Sdk.IOrganizationService]$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 $conn -pipelineValue ($PSBoundParameters.ContainsKey('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.Execute($exportRequest))

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

        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 Publish-CrmCustomization{
# .ExternalHelp Campgemini.Xrm.Data.PowerShell.Help.xml

 [CmdletBinding()]
    PARAM( 
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Sdk.IOrganizationService]$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 $conn -pipelineValue ($PSBoundParameters.ContainsKey('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.Execute($request)
        if($response.ResponseName -eq $null)
        {
            throw "Could not publish"
        }
    }
    catch
    {
        throw $_.Exception
    }    
}

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

 [CmdletBinding()]
    PARAM( 
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Sdk.IOrganizationService]$conn
    )

    $conn = VerifyCrmConnectionParam -conn $conn -pipelineValue ($PSBoundParameters.ContainsKey('conn'))  

    $request = New-Object Microsoft.Crm.Sdk.Messages.PublishAllXmlRequest
    
    try
    {
        $response = $conn.Execute($request)
    }
    catch
    {
        throw $_.Exception
    }    

    #return $response
}

function Set-CrmSolutionVersionNumber {
    [CmdletBinding()]
    PARAM(
        [parameter(Mandatory=$false)]
        [Microsoft.Xrm.Sdk.IOrganizationService]$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 $conn -pipelineValue ($PSBoundParameters.ContainsKey('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 $_.Exception
    }

    $result
}