Private/Add-Solution.ps1


#
# Add-Solution.ps1
#
function Add-Solution {
    $message = "Adding Dataverse Solution"
    Write-Host $message

    try {
        Invoke-AddSolution
    }
    catch {
        Write-Host $_
        pause
    }
    finally {
        $global:devops_configFile | ConvertTo-Json | Set-Content ("$env:APPDATA\Microsoft.PowerPlatform.DevOps\devopsConfig.json")
        if ($global:devops_projectFile) {
            $global:devops_projectFile | ConvertTo-Json | Set-Content ("$global:devops_projectLocation\$global:devops_gitRepo.json") 
        }
        Set-ProjectVariables
    }   
}

function Invoke-AddSolution {

    $logo = @"
 ____ ____ _ _ __ ____ ___
| _ \ _____ _____ _ __ | _ \| | __ _| |_ / _| ___ _ __ _ __ ___ | _ \ _____ __/ _ \ _ __ ___
| |_) / _ \ \ /\ / / _ \ '__| | |_) | |/ _' | __| |_ / _ \| '__| '_ ' _ \ | | | |/ _ \ \ / / | | | '_ \/ __|
| __/ (_) \ V V / __/ | | __/| | (_| | |_| _| (_) | | | | | | | | | |_| | __/\ V /| |_| | |_) \__ \
|_| \___/ \_/\_/ \___|_| |_| |_|\__,_|\__|_| \___/|_| |_| |_| |_| |____/ \___| \_/ \___/| .__/|___/
                                                                                                  |_|
 
"@


    $solmessage = @"
 
Welcome to the Power Platform DevOps Add Solution script. This script will perform the following steps automatically :
 
- Connect to Dataverse to bind to selected Solution
- Map Solution as Project in Visual Studio
 
ver. $global:devops_version
 
"@


    if (!$VerbosePreference -eq "Continue") {
    Clear-Host    
}
    Write-Host $logo -ForegroundColor Magenta
    Write-Host $solmessage -ForegroundColor White

    $quit = Read-Host -Prompt "Press Enter to Continue or [Q]uit"
    if ($quit -eq "Q") {
        return
    }

    try {
        $CreateOrSelectEnv = Read-Host -Prompt "Would you like to [P]rovision a new Dataverse Environment ? or [S]elect an Existing One (Default [S])"
        if ($CreateOrSelectEnv -eq "P") {
            Add-Environment
        }
        if ($global:devops_continue) {
            Invoke-SolutionAdder    
        }

    }
    catch {
        Write-Host $_
        pause    
    }
    finally {
        $global:devops_projectFile | ConvertTo-Json | Set-Content ("$global:devops_projectLocation\$global:devops_gitRepo.json") 
    }
}

function Add-Environment {
    Param(
        [Parameter(Mandatory = $False)][String]$DomainName = ""
    )
    try {
        #Install-PowerAppsAdmin
        Get-DataverseLogin
    
        $DisplayName = Read-Host -Prompt "Enter a DisplayName for the Environment (e.g. My Dataverse Development Environment)"
        if ($DomainName -eq "") {
            $DomainName = Read-Host -Prompt "Enter a Domain Name for the Environment (e.g. DVDevOpsDev01)"    
        }    
    
        $locations = Get-AdminPowerAppEnvironmentLocations
        $options = $locations | ForEach-Object { "$($_.LocationDisplayName)" }
    
        do {
            $sel = Invoke-Menu -MenuTitle "---- Please Select your Location ------" -MenuOptions $options
            $selectedLocation = $locations[$sel].LocationName
        } until ($selectedLocation -ne "")
        Write-Host $selectedLocation
    
        $selectedCurrency = (Get-AdminPowerAppCdsDatabaseCurrencies -LocationName $selectedLocation | Where-Object { $_.IsTenantDefaultCurrency -eq "True" }).CurrencyName
            
        $languages = Get-AdminPowerAppCdsDatabaseLanguages -LocationName $selectedLocation
        $options = $languages | ForEach-Object { "$($_.LanguageDisplayName)" }
        
        do {
            $sel = Invoke-Menu -MenuTitle "---- Please Select your Language ------" -MenuOptions $options
            $selectedanguage = $languages[$sel].LanguageName
        } until ($selectedanguage -ne "")
        Write-Host $selectedanguage
    
        $Templates = ""
        $options = @('Yes', 'No')
    
        do {
            $sel = Invoke-Menu -MenuTitle "---- Do you want to Install a Dataverse Template ------" -MenuOptions $options
            $useTemplate = $options[$sel]
        } until ($useTemplate -ne "")
                
        if ($useTemplate -eq 'Yes') {
            $templatelist = Get-AdminPowerAppCdsDatabaseTemplates -LocationName $selectedLocation | Where-Object { $_.IsDisabled -eq 0 }
            $options = $templatelist | ForEach-Object { "$($_.TemplateDisplayName)" }
                
            do {
                $sel = Invoke-Menu -MenuTitle "---- Please Select your Template ------" -MenuOptions $options
                $Templates = $templatelist[$sel].TemplateName
            } until ($TemplateName -ne "")
            Write-Host $TemplateName
            Write-Host "Provisioning .... this may take a few minutes"
            $EnvToCreate = New-AdminPowerAppEnvironment -DisplayName $DisplayName -LocationName $selectedLocation -EnvironmentSku "Sandbox" -ProvisionDatabase -CurrencyName $selectedCurrency -LanguageName $selectedanguage -Templates $Templates -DomainName $DomainName -WaitUntilFinished $true            
        }
        else {
            Write-Host "Provisioning .... this may take a few minutes"
            $EnvToCreate = New-AdminPowerAppEnvironment -DisplayName $DisplayName -LocationName $selectedLocation -EnvironmentSku "Sandbox" -ProvisionDatabase -CurrencyName $selectedCurrency -LanguageName $selectedanguage -DomainName $DomainName -WaitUntilFinished $true
                
        }
    
        if ($EnvToCreate.CommonDataServiceDatabaseProvisioningState -eq "Succeeded") {
            Write-Host Environment $DomainName Provisioned Succesfully
            if ($global:devops_DataverseCredType -eq "servicePrincipal") {
                Write-Host "Adding Access for Service Principal to Environment"
                Add-D365ApplicationUser -d365ResourceName $EnvToCreate.Internal.properties.linkedEnvironmentMetadata.instanceUrl -servicePrincipal $global:devops_ClientID -roleNames "System Administrator"
            }
            $global:devops_continue = $true
        }
        else {
            Write-Host "An Error occured trying to Provision Environment $DomainName, please try again or Provision it via the Admin Center"
            $global:devops_continue = $false
            Write-Host $EnvToCreate            
            pause 
        } 
    }
    catch {
        Write-Host $_
        pause 
    }

}

function Invoke-SolutionAdder {

    try {
        $message = "Connecting to Power Platform"
        Write-Host $message

        #Install-PowerAppsAdmin

        Get-DataverseLogin

        $connDev = Get-AdminPowerAppEnvironment
        $options = $connDev | ForEach-Object { "$($_.DisplayName) ($($_.Internal.properties.linkedEnvironmentMetadata.instanceUrl))" }

        do {
            $sel = Invoke-Menu -MenuTitle "---- Please Select your Development Environment ------" -MenuOptions $options
            $DevEnvironment = $connDev[$sel]
            $conn = Get-DataverseConnection($DevEnvironment.Internal.properties.linkedEnvironmentMetadata.instanceUrl)
        } until ($conn -ne "")

        if ($conn.IsReady) {
            $sel = $null
            $EnvironmentURL = $DevEnvironment.Internal.properties.linkedEnvironmentMetadata.instanceUrl
            Write-Host $EnvironmentURL

            $message = "Would you like to create a New Dataverse Solution or select an existing one"

            do {
                $sel = Invoke-Menu -MenuTitle "---- $message ------" -MenuOptions "Create New", "Select Existing"
        
            } until ($sel -ge 0)

            if ($sel -eq 0) {
                $sel = $null
                $message = "Creating Solution and Publisher"
                Write-Host $message
                if (Test-Path $global:devops_projectLocation\Publisher.zip) {
                    Write-Host "Publisher File found... Importing now"
                    Import-CrmSolution -conn $conn -SolutionFilePath $global:devops_projectLocation\Publisher.zip
                    $importedPublisher = $true
                }
                $message = "Create or select a Solution Publisher"
                do {
                    if ($importedPublisher) {
                        $sel = 1
                    }
                    else {
                        $sel = Invoke-Menu -MenuTitle "---- $message ------" -MenuOptions "Create New", "Select Existing"    
                    }            
                } until ($sel -ge 0)

                if ($sel -eq 0) {
       
                    do {
                        $PublisherName = Read-Host -Prompt "Enter a Name for your Solution Publisher"
                    }until($PublisherName -ne "")

                    do {
                        $PublisherPrefix = Read-Host -Prompt "Enter a Publisher Prefix"
                    }until($PublisherPrefix -ne "")
                    Write-Host "Creating Publisher..."
                    $PublisherId = New-CrmRecord -conn $conn -EntityLogicalName publisher -Fields @{"uniquename" = $PublisherName.Replace(' ', ''); "friendlyname" = $PublisherName; "customizationprefix" = $PublisherPrefix.Replace(' ', '').ToLower() }
                    Write-Host "Publisher ID - $PublisherId"
                    $PubLookup = New-CrmEntityReference -EntityLogicalName publisher -Id $PublisherId.Guid
                }
                else {
                    $publisherFetch = @"
    <fetch>
    <entity name='publisher' >
        <filter type='and' >
        <condition attribute='isreadonly' operator='eq' value='false' />
        </filter>
    </entity>
    </fetch>
"@


                    $publishers = (Get-CrmRecordsByFetch -conn $conn -Fetch $publisherFetch).CrmRecords
                    $options = $publishers | ForEach-Object { $($_.friendlyname) }  

                    do {
                        $choice = Invoke-Menu -MenuTitle "---- Select Publisher ------" -MenuOptions $options
                        $chosenPublisher = $publishers[$choice].customizationprefix
                    } until ($chosenPublisher -ne "")
                    if ($null -ne $chosenPublisher) {
                        $PublisherPrefix = $publishers[$choice].customizationprefix
                        $PubLookup = New-CrmEntityReference -EntityLogicalName publisher -Id $publishers[$choice].publisherid
                    }
                    else {
                        Write-Host "Invalid selection (index out of range)"
                    }
                }
                   
                do {
                    $SolutionName = Read-Host -Prompt "Enter a Name for your Unmanaged Development Solution"    
                }until ($SolutionName -ne "")

                $SolutionId = New-CrmRecord -conn $conn -EntityLogicalName solution -Fields @{"uniquename" = $SolutionName.Replace(' ', ''); "friendlyname" = $SolutionName; "version" = "1.0.0.0"; "publisherid" = $PubLookup }
                $chosenSolution = $SolutionName.Replace(' ', '')
            }
            else {

                $solutionFetch = @"
    <fetch>
    <entity name='solution' >
        <filter type='and' >
        <condition attribute='ismanaged' operator='eq' value='0' />
        <condition attribute='isvisible' operator='eq' value='1' />
        </filter>
    </entity>
    </fetch>
"@


                $solutions = (Get-CrmRecordsByFetch -conn $conn -Fetch $solutionFetch).CrmRecords
                $options = $solutions | ForEach-Object { $($_.uniquename) }  
                do {
                    $choice = Invoke-Menu -MenuTitle "---- Select Solution ------" -MenuOptions $options
                    $chosenSolution = $solutions[$choice].uniquename
                } until ($chosenSolution -ne "")

                if ($null -ne $chosenSolution) {
                    $PublisherPrefix = (Get-CrmRecord -conn $conn -EntityLogicalName publisher -Id $solutions[$choice].publisherid_Property.Value.Id -Fields customizationprefix).customizationprefix
                }
                else {
                    Write-Host "Invalid selection (index out of range)"
                }
            }
            #update values in Solution files
            $TextInfo = (Get-Culture).TextInfo

            $message = "Copying Solution Template to New $chosenSolution Project"
            Write-Host $message

            Remove-Item -Path ".\SolutionTemplate\node_modules", ".\SolutionTemplate\.awcache", ".\SolutionTemplate\bin", ".\SolutionTemplate\dist", ".\SolutionTemplate\obj" -Recurse -Force -ErrorAction SilentlyContinue
            Copy-Item -Path .\SolutionTemplate\. -Destination $chosenSolution -Recurse 

            $message = "Setting Configurations in Source Code"
            Write-Host $message

            Write-Host "Updating config.json ..."
            (Get-Content -Path .\$chosenSolution\Scripts\config.json) -replace "https://AddServer.crm6.dynamics.com", $EnvironmentURL | Set-Content -Path .\$chosenSolution\Scripts\config.json
            (Get-Content -Path .\$chosenSolution\Scripts\config.json) -replace "AddName", $chosenSolution | Set-Content -Path .\$chosenSolution\Scripts\config.json
            (Get-Content -Path .\$chosenSolution\package.json) -replace "solutiontemplate", $chosenSolution | Set-Content -Path .\$chosenSolution\package.json

            Write-Host "Updating spkl.json ..."
            (Get-Content -Path .\$chosenSolution\spkl\spkl.json) -replace "AddName", $chosenSolution | Set-Content -Path .\$chosenSolution\spkl\spkl.json
            (Get-Content -Path .\$chosenSolution\spkl\spkl.json) -replace "prefix", $PublisherPrefix.Replace(' ', '').ToLower() | Set-Content -Path .\$chosenSolution\spkl\spkl.json

            (Get-Content -Path .\$chosenSolution\webpack.common.js) -replace "AddName", $chosenSolution.ToLower() | Set-Content -Path .\$chosenSolution\webpack.common.js -ErrorAction Ignore

            Write-Host "Updating XrmContext.exe.config ..."
            (Get-Content -Path .\$chosenSolution\XrmContext\XrmContext.exe.config) -replace "AddName", $chosenSolution | Set-Content -Path .\$chosenSolution\XrmContext\XrmContext.exe.config

            Write-Host "Updating XrmDefinitelyTyped.exe.config ..."
            (Get-Content -Path .\$chosenSolution\XrmDefinitelyTyped\XrmDefinitelyTyped.exe.config) -replace "AddName", $chosenSolution | Set-Content -Path .\$chosenSolution\XrmDefinitelyTyped\XrmDefinitelyTyped.exe.config

            Write-Host "Rename SolutionTemplate.csproj to $chosenSolution.csproj"
            Rename-Item -Path .\$chosenSolution\SolutionTemplate.csproj -NewName "$chosenSolution.csproj"

            Write-Host "Rename dot files"
            Rename-Item -Path .\$chosenSolution\file.gitignore -NewName ".gitignore" -ErrorAction SilentlyContinue

            Write-Host "Rename SolutionTemplate.snk to $chosenSolution.snk"
            Rename-Item -Path .\$chosenSolution\SolutionTemplate.snk -NewName "$chosenSolution.snk"

            Write-Host "Updating $chosenSolution.csproj ..."
            (Get-Content -Path .\$chosenSolution\$chosenSolution.csproj) -replace "FeatureTemplate", $chosenSolution | Set-Content -Path .\$chosenSolution\$chosenSolution.csproj
            (Get-Content -Path .\$chosenSolution\$chosenSolution.csproj) -replace "SolutionTemplate", $chosenSolution | Set-Content -Path .\$chosenSolution\$chosenSolution.csproj

            Write-Host "Update Sample Plugin NameSpace"
            (Get-Content -Path .\$chosenSolution\Plugins\Users\SamplePlugin.cs) -replace "AddName", $chosenSolution | Set-Content -Path .\$chosenSolution\\Plugins\Users\SamplePlugin.cs -ErrorAction Ignore

            Write-Host "Adding Solution to packageDeploy.json"
            $packagesToDeploy = Get-Content .\deployPackages.json -Encoding UTF8 | ConvertFrom-Json
            if ($global:devops_projectFile.CICDFriendlyName.Length -gt 0) {
                $flowJSON = @{ActivateFlows = "true"; OverrideFile = ""; FailonError = "true";}
                $deployTo = @([ordered]@{EnvironmentName = $global:devops_projectFile.CICDFriendlyName; DeploymentType = "Managed"; DeployData = "false"; PreAction = "false"; PostAction = "false"; DeployAsHolding="true"; PreUpgrade = "false"; PostUpgrade = "false"; PowerAppsChecker="true"; Flows= $flowJSON })    
            }    
            else {
                $deployTo = @()
            }        
            if ($packagesToDeploy.Count -gt 0) {
                $packagesToDeploy += [ordered]@{SolutionName = $chosenSolution; DeploymentSteps = ""; DeployTo = $deployTo }     
                ConvertTo-Json -Depth 4 $packagesToDeploy | Format-Json | Out-File .\deployPackages.json -Encoding utf8
            }
            else {
                ConvertTo-Json -Depth 4 @([ordered]@{SolutionName = $chosenSolution; DeploymentSteps = ""; DeployTo = $deployTo }) | Format-Json | Out-File .\deployPackages.json -Encoding utf8
            }

            Set-Location -Path  .\$chosenSolution
    
            Write-Host "Adding $chosenSolution Project to Solution"
            Set-Location .\..
            $sln = Get-ChildItem *.sln
            dotnet sln $sln.Name add $chosenSolution\$chosenSolution.csproj
            dotnet sln $sln.Name remove SolutionTemplate\SolutionTemplate.csproj

            git add -A
            git commit -m "Added Solution $chosenSolution"

            $global:devops_projectFile.DataverseSolutions += [ordered]@{SolutionName = $chosenSolution; ID = $global:devops_projectFile.DataverseSolutions.Count }  
            
            Invoke-ExportSolution -StartPath $global:devops_projectLocation -SelectedSolution $chosenSolution

        }
        else {
            Write-Host $conn.LastCrmError
            pause
        }
        
    }
    catch {
        Write-Host $_
        pause
    }
}