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 | Out-FileUtf8NoBom ("$env:APPDATA\Capgemini.PowerPlatform.DevOps\devopsConfig.json") if ($global:devops_projectFile) { $global:devops_projectFile | ConvertTo-Json | Out-FileUtf8NoBom ("$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 | Out-FileUtf8NoBom ("$global:devops_projectLocation\$global:devops_gitRepo.json") } } function Add-Environment { Param( [Parameter(Mandatory = $False)][String]$DomainName = "" ) try { 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 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 | Out-FileUtf8NoBom .\$chosenSolution\Scripts\config.json (Get-Content -Path .\$chosenSolution\Scripts\config.json) -replace "AddName", $chosenSolution | Out-FileUtf8NoBom .\$chosenSolution\Scripts\config.json (Get-Content -Path .\$chosenSolution\package.json) -replace "solutiontemplate", $chosenSolution | Out-FileUtf8NoBom .\$chosenSolution\package.json Write-Host "Updating spkl.json ..." (Get-Content -Path .\$chosenSolution\spkl\spkl.json) -replace "AddName", $chosenSolution | Out-FileUtf8NoBom .\$chosenSolution\spkl\spkl.json (Get-Content -Path .\$chosenSolution\spkl\spkl.json) -replace "prefix", $PublisherPrefix.Replace(' ', '').ToLower() | Out-FileUtf8NoBom .\$chosenSolution\spkl\spkl.json (Get-Content -Path .\$chosenSolution\webpack.common.js) -replace "AddName", $chosenSolution.ToLower() | Out-FileUtf8NoBom .\$chosenSolution\webpack.common.js -ErrorAction Ignore Write-Host "Updating XrmContext.exe.config ..." (Get-Content -Path .\$chosenSolution\XrmContext\XrmContext.exe.config) -replace "AddName", $chosenSolution | Out-FileUtf8NoBom .\$chosenSolution\XrmContext\XrmContext.exe.config Write-Host "Updating XrmDefinitelyTyped.exe.config ..." (Get-Content -Path .\$chosenSolution\XrmDefinitelyTyped\XrmDefinitelyTyped.exe.config) -replace "AddName", $chosenSolution | Out-FileUtf8NoBom .\$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 | Out-FileUtf8NoBom .\$chosenSolution\$chosenSolution.csproj (Get-Content -Path .\$chosenSolution\$chosenSolution.csproj) -replace "SolutionTemplate", $chosenSolution | Out-FileUtf8NoBom .\$chosenSolution\$chosenSolution.csproj Write-Host "Update Sample Plugin NameSpace" (Get-Content -Path .\$chosenSolution\Plugins\Users\SamplePlugin.cs) -replace "AddName", $chosenSolution | Out-FileUtf8NoBom .\$chosenSolution\\Plugins\Users\SamplePlugin.cs -ErrorAction Ignore Write-Host "Adding Solution to packageDeploy.json" $packagesToDeploy = Get-Content .\deployPackages.json | ConvertFrom-Json if ($global:devops_projectFile.CICDFriendlyName.Length -gt 0) { $flowJSON = @{ActivateFlows = "true"; OverrideFile = ""; FailonError = "false"; } $deployTo = @([ordered]@{EnvironmentName = $global:devops_projectFile.CICDFriendlyName; Deploy="true"; DeploymentType = "Managed"; DeployData = "false"; LegacyDataImport="false"; PreAction = "false"; PostAction = "false"; DeployAsHolding = "true"; StageForUpgrade = "false"; OverwriteUnmanagedCustomisations="false"; PreUpgrade = "false"; PostUpgrade = "false"; CleanupAction = "false"; AlwaysDeployData = "false"; PowerAppsChecker = "false"; Flows = $flowJSON }) } else { $deployTo = @() } if ($packagesToDeploy.Count -gt 0) { $packagesToDeploy += [ordered]@{SolutionName = $chosenSolution; DeploymentSteps = ""; DeployTo = $deployTo } ConvertTo-Json -Depth 4 $packagesToDeploy | Format-Json | Out-FileUtf8NoBom .\deployPackages.json } else { ConvertTo-Json -Depth 4 @([ordered]@{SolutionName = $chosenSolution; DeploymentSteps = ""; DeployTo = $deployTo }) | Format-Json | Out-FileUtf8NoBom .\deployPackages.json } 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 } } |