Public/Sync-CrmSolutionFromSource.ps1

Function Sync-CrmSolutionFromSource {
    <#
        .SYNOPSIS
            Builds a Solution in a Dynamics Crm org based on a source solution
            in another org.
     
        .DESCRIPTION
            Builds a Solution in a Dynamics Crm org based on a source solution
            in another org.
 
            Necessary parameters read from config file. File can be generated
            using the '-GenerateConfig' switch -- this will prompt you for
            the required parameters -- or via the 'New-CrmSolutionFromSourceConfig' cmdlet.
 
            If Solution does not already exist on target, it will be created. If publisher
            does not already exist on target, it will be created. All publisher info and
            solution name are assumed to be the same on source and target.
 
            WARNING: If the target Solution exists and contains SolutionComponents, any
            SolutionComponents not in source Solution will either (a) be removed from
            the target Solution if they exist in other Solutions on the target org, or
            (b) deleted if they exist in target solution only and nowhere else.
 
            If specified in config, entities with rootcomponentbehaviors of 0 will be
            changed to 1.
 
            This effectively Syncs the two solutions, cleaning 'rogue' SolutionComponents from
            the target org.
 
        .OUTPUTS
            -Solution on target Dynamics Crm Org.
 
            -'Errorlog.txt', if errors encountered.
 
            -'BuildCrmSolutionLog.json', contains useful information about SolutionComponents
             manipulated by the cmdlet. This includes those Added, Skipped, and comparison results.
    #>

    [cmdletbinding()]
    Param 
    (
        #Credentials for Source environment
        [Parameter(ParameterSetName='Cred')]
        [pscredential]$SourceCredential,

        #Credentials for Target environment
        [Parameter(ParameterSetName='Cred')]
        [pscredential]$TargetCredential,

        #Username for Source environment
        [Parameter(ParameterSetName='DiscreteCreds', Mandatory=$true)]
        [string]$SourceUsername,

        #Password for Source environment
        [Parameter(ParameterSetName='DiscreteCreds', Mandatory=$true)]
        [securestring]$SourcePassword,

        #Username for Target environment
        [Parameter(ParameterSetName='DiscreteCreds', Mandatory=$true)]
        [string]$TargetUsername,

        #Password for Target environment
        [Parameter(ParameterSetName='DiscreteCreds', Mandatory=$true)]
        [securestring]$TargetPassword,

        #Path to existing config file
        [Parameter(Mandatory=$true)]
        [string]$ConfigFilePath
    )

    try {
        $config = Import-PowerShellDataFile $PWD/$ConfigFilePath
    } catch {
        $err = $_.Exception.Message
        $err | Out-File errorlog.txt -Append
        throw $err
    }

    $sourceOrg                  = $config.SourceOrg  
    $targetOrg                  = $config.TargetOrg
    $fixRootComponentBehavior   = $config.FixRootComponentBehavior
    $solutionName               = $config.SolutionName
    $publisher                  = $config.Publisher
    
    $components = @{}
    $log = @{}
    
    if ($PSBoundParameters.ContainsKey('SourcePassword')){
        $sourceConn = Get-CrmConnection -Username $SourceUsername -Password $SourcePassword -Url "https://$sourceOrg.crm.dynamics.com" 
        $targetConn = Get-CrmConnection -Username $TargetUsername -Password $TargetPassword -Url "https://$targetOrg.crm.dynamics.com"
    } else {
        $sourceConn = Get-CrmConnection -Cred $SourceCredential -Url "https://$sourceOrg.crm.dynamics.com" 
        $targetConn = Get-CrmConnection -Cred $TargetCredential -Url "https://$targetOrg.crm.dynamics.com"
    }
    
    $sourceSolutionExists = Test-CrmSolutionExists -Conn $sourceConn -SolutionName $solutionName
    $targetSolutionExists = Test-CrmSolutionExists -Conn $targetConn -SolutionName $solutionName    
    
    if (-Not $sourceSolutionExists) {
        $err = "Source solution does not exist"
        $err | Out-File -Append 'errorlog.txt'
        throw $err 
    }    
    
    if (-Not $targetSolutionExists) {
        New-CrmSolution -SolutionName $solutionName -Publisher $publisher -Conn $targetConn
        $log["TemplateCreaded"] = $true 
    }   

    if ($fixRootComponentBehavior){
        $components["PreUpdateSource"] = Get-CrmSolutionComponent -Conn $sourceConn -SolutionName $solutionName
        $components["RootComponentBehaviorUpdated"] = Update-RootComponentBehavior -Component $components.PreUpdateSource -SolutionName $solutionName -Conn $sourceConn 
    }

    $components["Source"] = @{"Original" = Get-CrmSolutionComponent -Conn $sourceConn -SolutionName $solutionName} 
    $components["Target"] = @{"Original" = Get-CrmSolutionComponent -Conn $targetConn -SolutionName $solutionName}

    Write-Verbose 'Pulling down metadata...'
    $metadata = @{
        "Source" = Get-CrmEntityAllMetadata -Conn $sourceConn -EntityFilters All | MetadataToHash;
        "Target" = Get-CrmEntityAllMetadata -Conn $targetConn -EntityFilters All | MetadataToHash
    }
    Write-Verbose 'Updating source components against metadata...'
    $components.Source["Updated"] = Update-ComponentAgainstMetadata -SolutionComponent $components.Source.Original -MetadataSource $metadata.source -MetadataTarget $metadata.target -SourceConn $sourceConn -TargetConn $targetConn

    if ($components.Source.Updated -and 
        $components.Target.Original) {
        [SolutionComponent[]]$components.Source["SourceOnly"] = Compare-CrmSolutionComponent -ReferenceComponent $components.Source.Updated -DifferenceComponent $components.Target.Original
        [SolutionComponent[]]$components.Target["TargetOnly"] = Compare-CrmSolutionComponent -ReferenceComponent $components.Target.Original -DifferenceComponent $components.Source.Updated
    }

    if ($components.Source.SourceOnly) { 
        $components["Manifest"] = Compare-ComponentsWithTargetOrg -Component $components.Source.SourceOnly -Metadata $metadata -SourceConn $SourceConn -TargetConn $targetConn
    } else {
        $components["Manifest"] = Compare-ComponentsWithTargetOrg -Component $components.Source.Updated -Metadata $metadata -SourceConn $SourceConn -TargetConn $targetConn
    }
    
    # $components.Manifest["Removals"] = $components.Target.TargetOnly

    Write-ManifestOut -Manifest $components.Manifest
    
    <# if ($components.Manifest.Removals){
        Remove-Component -Component $components.Manifest.Removals -Conn $targetConn -SolutionName $solutionName | Out-Null
    } #>


    if ($components.Manifest.Add) {
        Add-Component -Conn $targetConn -Component $components.Manifest.Add -SolutionName $solutionName
    }

    
    
    $log["Timestamp"] = (Get-Date -Format o)
    $log["Config"] = $config
    $log["Components"] = $components
    ConvertTo-Json -InputObject $log -Depth 10 | Out-File -FilePath "BuildCrmSolutionLog.json"
    Write-Verbose "Data log saved to BuildSolutionLog.json"
    if (Test-Path 'errorlog.txt') {
        Write-Verbose 'Errors logged to errorlog.txt'
    } else {
        Write-Verbose 'No errors logged.'
    }
}