Microsoft.PowerPlatform.DevOps.psm1
Write-Verbose "Importing Functions" # Import everything in these folders foreach ($folder in @('Private', 'Public', 'Classes')) { $root = Join-Path -Path $PSScriptRoot -ChildPath $folder if (Test-Path -Path $root) { Write-Verbose "processing folder $root" $files = Get-ChildItem -Path $root -Filter *.ps1 # dot source each file $files | where-Object { $_.name -NotLike '*.Tests.ps1' } | ForEach-Object { Write-Verbose $_.name; . $_.FullName } } } $setupTools = Join-Path -Path $PSScriptRoot -ChildPath "FrameworkTemplate\Solutions\Scripts\_SetupTools.ps1" . $setupTools if (!(Test-Path "$env:APPDATA\Microsoft.PowerPlatform.DevOps")) { New-Item -Path "$env:APPDATA\Microsoft.PowerPlatform.Devops" -ItemType Directory } if (!(Test-Path "$env:APPDATA\Microsoft.PowerPlatform.DevOps\devopsConfig.json")) { Copy-Item (Join-Path $PSScriptRoot "\devopsConfig.json") -Destination "$env:APPDATA\Microsoft.PowerPlatform.DevOps\devopsConfig.json" } function Install-PreReqs { $message = "Checking Pre-requisites" Write-Host $message try { Invoke-InstallPreRequisites $global:devops_configFile.PreReqsComplete = "True" } catch { $global:devops_configFile.PreReqsComplete = "Error" pause } $global:devops_configFile | ConvertTo-Json | Set-Content ("$env:APPDATA\Microsoft.PowerPlatform.DevOps\devopsConfig.json") } function Connect-AzureDevOps { $message = "Configuring Azure DevOps" Write-Host $message try { Invoke-AzureDevOps $global:devops_projectFile.ADOConfigured = "True" } catch { $global:devops_projectFile.ADOConfigured = "Error" pause } $global:devops_projectFile | ConvertTo-Json | Set-Content ("$global:devops_projectLocation\$global:devops_gitRepo.json") } function Add-Solution { $message = "Adding D365 / CDS Solution" Write-Host $message try { Invoke-AddSolution $global:devops_projectFile.SolutionAdded = "True" } catch { $global:devops_projectFile.SolutionAdded = "Error" pause } $global:devops_projectFile | ConvertTo-Json | Set-Content ("$global:devops_projectLocation\$global:devops_gitRepo.json") } function Add-CICDEnvironment { $message = "Configuring CI/CD Environment" Write-Host $message try { $CreateOrSelectEnv = Read-Host -Prompt "CI/CD Environment : Would you like to [P]rovision a new Power Platform Environment ? or [S]elect an Existing One (Default [S])" if ($CreateOrSelectEnv -eq "P") { Add-Environment Invoke-ConfigureCICD } else { Invoke-ConfigureCICD } } catch { $global:devops_projectFile.CICDEnvironmentName = "Error" pause } $global:devops_projectFile | ConvertTo-Json | Set-Content ("$global:devops_projectLocation\$global:devops_gitRepo.json") } function Add-Project { if ($global:devops_configFile.Projects.Count -gt 1 -or $global:devops_configFile.Projects[0].ID -ne "placeholder") { $message = "Adding PowerPlatform DevOps Project" $options = "Select and Existing Project", "Browse for a local Repository", "Create a new Project", "Clone an existing Repo", "Quit" do { $sel = Invoke-Menu -MenuTitle "---- $message ------" -MenuOptions $options } until ($sel -ge 0) switch ($sel) { '0' { Select-GitRepo } '1' { Get-GitRepo } '2' { Add-GitRepo } '3' { Clone-GitRepo } '4' { return } } } else { Add-GitRepo } try { } catch { $global:devops_projectFile.SolutionAdded = "Error" pause } $global:devops_projectFile | ConvertTo-Json | Set-Content ("$global:devops_projectLocation\$global:devops_gitRepo.json") $global:devops_configFile | ConvertTo-Json | Set-Content ("$env:APPDATA\Microsoft.PowerPlatform.DevOps\devopsConfig.json") } function Export-Solution { if ($global:devops_projectFile.CDSSolutions.Count -gt 0) { $options = $global:devops_projectFile.CDSSolutions | ForEach-Object { $_.SolutionName } do { $sel = Invoke-Menu -MenuTitle "---- Please Select the Solution to Export and Unpack ------" -MenuOptions $options $selectedSolution = $global:devops_projectFile.CDSSolutions[$sel].SolutionName } until ($selectedSolution -ne "") Write-Host $selectedSolution Write-Host $global:devops_projectLocation Set-Location -Path "$global:devops_projectLocation\$selectedSolution\Scripts" if ($global:devops_projectFile.ADOConfigured -eq "True") { try { git pull origin } catch { pause } } & .\SolutionExport.ps1 -DevMode $global:devops_devMode Set-Location -Path $global:devops_projectLocation } } function Sync-Solution { $commitMessage = Read-Host "Enter a description for your Commit" git add -A git commit -m $commitMessage if ($global:devops_projectFile.ADOConfigured -eq "True") { git push origin } pause } function Show-SolutionChanges { git status pause } function Enable-AzureDeploy { if ($global:devops_projectFile.ARMAdded -ne "True") { Copy-Item -Path (Join-Path $PSScriptRoot Snippets\AzureResources\.) -Destination $global:devops_projectLocation -Recurse -Force $buildYAML = Get-Content -Path "$global:devops_projectLocation\Build.yaml" $azureYAML = Get-Content -Path (Join-Path $PSScriptRoot Snippets\AzureDeploy.yaml) $azureYAML = $azureYAML.Replace('replaceRepo', $global:devops_projectFile.gitRepo) $buildYAML + $azureYAML | Set-Content -Path "$global:devops_projectLocation\Build.yaml" $azureParams = Get-Content -Path "$global:devops_projectLocation\AzureResources\azuredeploy.parameters.json" $azureParams = $azureParams.Replace('AddName', $global:devops_projectFile.gitRepo) $azureParams | Set-Content -Path "$global:devops_projectLocation\AzureResources\azuredeploy.parameters.json" $adoOrg = $global:devops_projectFile.ADOOrgName $adoProject = $global:devops_projectFile.ADOProject $adoRepo = $global:devops_projectFile.gitRepo.ToLower() if ($adoRepo.Length -gt 12) { $adoRepoShort = $adoRepo.Substring(0, 12) } else { $adoRepoShort = $adoRepo } $varGroupAzure = az pipelines variable-group create --organization https://dev.azure.com/$adoOrg --project $adoProject --name "$($global:devops_projectFile.gitRepo).AzureEnvironment" --variables AzureResourceGroup="" --authorize $true | ConvertFrom-Json az pipelines variable-group variable create --organization https://dev.azure.com/$adoOrg --project $adoProject --name AzureAppInsightsName --value "$adoRepo-staging-ai" --group-id $varGroupAzure.id az pipelines variable-group variable create --organization https://dev.azure.com/$adoOrg --project $adoProject --name AzureLocation --value "australiaeast" --group-id $varGroupAzure.id az pipelines variable-group variable create --organization https://dev.azure.com/$adoOrg --project $adoProject --name AzureStorageAccountName --value "$($adoRepo)staging" --group-id $varGroupAzure.id az pipelines variable-group variable create --organization https://dev.azure.com/$adoOrg --project $adoProject --name AzureFunctionAppName --value "$adoRepo-staging-fna" --group-id $varGroupAzure.id az pipelines variable-group variable create --organization https://dev.azure.com/$adoOrg --project $adoProject --name AzureWebAppName --value "$adoRepo-staging-wba" --group-id $varGroupAzure.id az pipelines variable-group variable create --organization https://dev.azure.com/$adoOrg --project $adoProject --name WorkspaceName --value "$adoRepo-staging-log" --group-id $varGroupAzure.id az pipelines variable-group variable create --organization https://dev.azure.com/$adoOrg --project $adoProject --name AzureKeyVaultName --value "$adoRepoShort-staging-kv" --group-id $varGroupAzure.id $global:devops_projectFile.ARMAdded = "True" $global:devops_projectFile | ConvertTo-Json | Set-Content ("$global:devops_projectLocation\$global:devops_gitRepo.json") Write-Host "Please make sure you follow the instructions for configuring a Service Connection in Azure DevOps, as detailed in $global:devops_projectLocation\AzureResources\Instructions.md" -ForegroundColor Yellow Write-Host "Press Enter to Continue..." -ForegroundColor White pause } else { Write-Host "Azure Deployment Already Enabled for this Project" pause } } function Enable-FunctionApp { if ($global:devops_projectFile.ARMAdded -eq "True") { if ($global:devops_projectFile.FunctionAppAdded -ne "True") { Copy-Item -Path (Join-Path $PSScriptRoot Snippets\FunctionApp\.) -Destination $global:devops_projectLocation -Recurse -Force dotnet sln $global:devops_gitRepon.sln add FunctionApp\FunctionApp.csproj $buildYAML = Get-Content -Path "$global:devops_projectLocation\Build.yaml" $azureYAML = Get-Content -Path (Join-Path $PSScriptRoot Snippets\FunctionAppDeploy.yaml) $azureYAML = $azureYAML.Replace('replaceRepo', $global:devops_projectFile.gitRepo) $buildYAML + $azureYAML | Set-Content -Path "$global:devops_projectLocation\Build.yaml" $global:devops_projectFile.FunctionAppAdded = "True" $global:devops_projectFile | ConvertTo-Json | Set-Content ("$global:devops_projectLocation\$global:devops_gitRepo.json") } else { Write-Host "Function App Already Enabled for this Project" pause } } else { Write-Host "Function App Requires that you first Enable Azure Resource Management Deployment" pause } } function Invoke-OpenSolution { . "$global:devops_projectLocation\$global:devops_gitRepo.sln" } function Show-Menu { param ( [string]$Title = 'Power Platform DevOps' ) $devopsConfigMessage = "(ADO Org : $($global:devops_projectFile.ADOOrgName) | ADO Project : $($global:devops_projectFile.ADOProject) | git Repo : $($global:devops_projectFile.gitRepo))" $CICDConfigMessage = "(CI/CD Environment : $($global:devops_projectFile.CICDEnvironmentName) | CI/CD URL : $($global:devops_projectFile.CICDEnvironmentURL))" if ($global:devops_projectFile -eq "False") { $Title = "Power Platform DevOps (No Project Selected)" } else { $Title = "Power Platform DevOps ($($global:devops_projectFile.gitRepo))" $global:devops_projectTitle = $global:devops_projectFile.gitRepo Set-Location $global:devops_projectLocation } $global:devops_message = @" Welcome to the Power Platform DevOps script. ver: $global:devops_version (latest available version : $latestVersion) project: $global:devops_projectTitle repo : $global:devops_gitRepo azure subscription : $global:devops_selectedSubscriptionName ($global:devops_selectedSubscription) azure keyvault : $global:devops_AzureKeyVault tenant id : $global:devops_TenantID client id : $global:devops_ClientID "@ [console]::ForegroundColor = "White" Clear-Host Write-Host $global:devops_logo -ForegroundColor Magenta Write-Host $global:devops_message -ForegroundColor White $Repeater = "=" * $Title.Length Write-Host "================ $Title ================" -ForegroundColor White Write-Host "1: Run Pre-requisite checks (Install / Update)." -ForegroundColor (Set-ColourOption $global:devops_configFile.PreReqsComplete) if (($global:devops_configFile.PreReqsComplete -eq "True")) { Write-Host "2: Create a New Project or Select Existing" -ForegroundColor (Set-ColourOption $global:devops_projectFile) } if (!($global:devops_projectFile -eq "False") -and ($global:devops_configFile.PreReqsComplete -eq "True")) { Write-Host "3: Configure Azure DevOps" $devopsConfigMessage -ForegroundColor (Set-ColourOption $global:devops_projectFile.ADOConfigured) Write-Host "4: Add New D365 / CDS Solution." -ForegroundColor (Set-ColourOption $global:devops_projectFile.SolutionAdded) if ($global:devops_projectFile.ADOConfigured -eq "True") { Write-Host "5: Configure Continuous Deployment" $CICDConfigMessage -ForegroundColor (Set-ColourOption $global:devops_projectFile.CICDEnvironmentName) } Write-Host "A: Enable [A]zure Resource Management Deployment." -ForegroundColor (Set-ColourOption $global:devops_projectFile.ARMAdded) Write-Host "F: Add Azure [F]unction App Project." -ForegroundColor (Set-ColourOption $global:devops_projectFile.FunctionAppAdded) if ($global:devops_projectFile.SolutionAdded -eq "True") { Write-Host "D: Add Additional [D]365 / CDS Solutions" -ForegroundColor Cyan Write-Host "E: [E]xport & Unpack Solution to Source Control" -ForegroundColor Cyan } if ($global:devops_projectFile.ADOConfigured -eq "True") { Write-Host "S: Commit and [S]ync changes to Source Control" -ForegroundColor Cyan Write-Host "V: [V]iew current change log for Source Control" -ForegroundColor Cyan if ($global:devops_projectFile.CICDEnvironmentName -ne "False") { Write-Host "T: Add Additional Deployment Environment [T]arget" -ForegroundColor Cyan } } Write-Host "O: [O]pen Project in Visual Studio" -ForegroundColor Cyan } Write-Host "C: Advanced [C]onfiguration Management" -ForegroundColor Yellow Write-Host "U: Check for [U]pdates to Microsoft.PowerPlatform.DevOps" -ForegroundColor Cyan Write-Host "Q: Press 'Q' to quit." -ForegroundColor White Write-Host "=================$Repeater=================" -ForegroundColor White Write-Host "" Write-Host "* (White items still need to be completed)" -ForegroundColor White Write-Host "* (Green items are successful)" -ForegroundColor Green Write-Host "* (Red items have Errors)" -ForegroundColor Red Write-Host "* (Purple items are optional)" -ForegroundColor Magenta Write-Host "* (Blue Items are used for Day-to-Day ALM)" -ForegroundColor Cyan Write-Host "" if ($latestVersion -gt $global:devops_version) { Write-Host "There is a newer version available, please run Update-Module Microsoft.PowerPlatform.DevOps to update" -ForegroundColor Yellow Write-Host "" } } function Show-ConfigMenu { param ( [string]$Title = 'Power Platform DevOps' ) $devopsConfigMessage = "(ADO Org : $($global:devops_projectFile.ADOOrgName) | ADO Project : $($global:devops_projectFile.ADOProject) | git Repo : $($global:devops_projectFile.gitRepo))" $CICDConfigMessage = "(CI/CD Environment : $($global:devops_projectFile.CICDEnvironmentName) | CI/CD URL : $($global:devops_projectFile.CICDEnvironmentURL))" if ($global:devops_projectFile -eq "False") { $Title = "Power Platform DevOps (No Project Selected)" } else { $Title = "Power Platform DevOps ($($global:devops_projectFile.gitRepo))" $global:devops_projectTitle = $global:devops_projectFile.gitRepo Set-Location $global:devops_projectLocation } $global:devops_message = @" Power Platform DevOps Configuration. ver: $global:devops_version (latest available version : $latestVersion) project: $global:devops_projectTitle repo : $global:devops_gitRepo azure subscription : $global:devops_selectedSubscriptionName ($global:devops_selectedSubscription) azure keyvault : $global:devops_AzureKeyVault tenant id : $global:devops_TenantID client id : $global:devops_ClientID "@ [console]::ForegroundColor = "White" Clear-Host Write-Host $global:devops_logo -ForegroundColor Magenta Write-Host $global:devops_message -ForegroundColor White $Repeater = "=" * $Title.Length Write-Host "================ $Title ================" -ForegroundColor White Write-Host "G: Edit [G]lobal Config File" -ForegroundColor Yellow if ($global:devops_projectFile -ne "False") { Write-Host "P: Edit [P]roject Config File" -ForegroundColor Yellow Write-Host "A: Setup [A]zure KeyVault" -ForegroundColor Cyan Write-Host "C: [C]onfigure Service Principal" -ForegroundColor Cyan } Write-Host "Q: Press 'Q' to return to Main Menu." -ForegroundColor White Write-Host "=================$Repeater=================" -ForegroundColor White Write-Host "" $configSelection = Read-Host "Selection" switch ($configSelection) { 'G' { . $env:APPDATA\Microsoft.PowerPlatform.DevOps\devopsConfig.json } 'P' { . $global:devops_projectLocation\$global:devops_gitRepo.json } 'A' { Configure-AzureKeyVault } 'C' { Configure-ServicePrincipal } } } function Invoke-PowerPlatformDevOps { Param( [string] [Parameter(Mandatory = $false)] $PreLoadProjectPath = "", [string] [Parameter(Mandatory = $false)] $PreLoadedProjectName = "", [string] [Parameter(Mandatory = $false)] $PreSelection = "" ) $global:devops_configFile = Get-Content ("$env:APPDATA\Microsoft.PowerPlatform.DevOps\devopsConfig.json") | ConvertFrom-Json [string]$getVersion = Get-ManifestValue (Join-Path $PSScriptRoot "\Microsoft.PowerPlatform.DevOps.psd1") -PropertyName 'ModuleVersion' -Passthru [version]$global:devops_version = $getVersion.Replace("'", "") $global:devops_projectFile = "False" $global:devops_gitRepo = "" $global:devops_projectLocation = "" $global:devops_continue = $true $global:devops_devMode = $false if ($global:devops_projectFile -eq "False") { $global:devops_projectTitle = "(No Project Selected)" $global:devops_gitRepo = "(No Project Selected)" $global:devops_selectedSubscriptionName = "(No Project Selected)" $global:devops_AzureKeyVault = "(No Project Selected)" $global:devops_ClientID = "(No Project Selected)" $global:devops_TenantID = "(No Project Selected)" } else { $global:devops_projectTitle = $global:devops_projectFile.gitRepo $global:devops_selectedSubscription = $global:devops_projectFile.selectedSubscription $global:devops_selectedSubscriptionName = $global:devops_projectFile.selectedSubscriptionName $global:devops_AzureKeyVault = $global:devops_projectFile.AzureKeyVaultName $global:devops_ClientID = $global:devops_projectFile.ClientID } $regKey = "hklm:/software/microsoft/windows nt/currentversion" $global:devops_Core = (Get-ItemProperty $regKey).InstallationType -eq "Server Core" $global:devops_logo = @" ____ ____ _ _ __ ____ ___ | _ \ _____ _____ _ __ | _ \| | __ _| |_ / _| ___ _ __ _ __ ___ | _ \ _____ __/ _ \ _ __ ___ | |_) / _ \ \ /\ / / _ \ '__| | |_) | |/ _` | __| |_ / _ \| '__| '_ ` _ \ | | | |/ _ \ \ / / | | | '_ \/ __| | __/ (_) \ V V / __/ | | __/| | (_| | |_| _| (_) | | | | | | | | | |_| | __/\ V /| |_| | |_) \__ \ |_| \___/ \_/\_/ \___|_| |_| |_|\__,_|\__|_| \___/|_| |_| |_| |_| |____/ \___| \_/ \___/| .__/|___/ |_| "@ if ($PreLoadProjectPath -ne "") { if ((Test-Path -Path "$PreLoadProjectPath\$PreLoadedProjectName.json")) { $global:devops_projectLocation = "$PreLoadProjectPath" $global:devops_projectFile = Get-Content ("$PreLoadProjectPath\$PreLoadedProjectName.json") | ConvertFrom-Json $global:devops_gitRepo = $global:devops_projectFile.gitRepo Set-Location $global:devops_projectLocation } } Write-Host "" [console]::ForegroundColor = "White" do { $global:devops_configFile = Get-Content ("$env:APPDATA\Microsoft.PowerPlatform.DevOps\devopsConfig.json") | ConvertFrom-Json if ($global:devops_projectFile -ne "False") { Set-Location $global:devops_projectLocation } Show-Menu if ($PreSelection -ne "") { $selection = $PreSelection $global:devops_devMode = $true $PreSelection = "" Write-Host "Performing your Automated Selection ..." Start-Sleep -Seconds 2 } else { $selection = Read-Host "Please make a selection" } switch ($selection) { '1' { Install-PreReqs } '2' { Add-Project } '3' { Connect-AzureDevOps } '4' { Add-Solution } '5' { Add-CICDEnvironment } 'D' { Add-Solution } 'E' { Export-Solution } 'S' { Sync-Solution } 'V' { Show-SolutionChanges } 'T' { $CreateOrSelectEnv = Read-Host -Prompt "CI/CD Environment : Would you like to [P]rovision a new Power Platform Environment ? or [S]elect an Existing One (Default [S])" if ($CreateOrSelectEnv -eq "P") { Add-Environment Invoke-AddEnvironments } else { Invoke-AddEnvironments } } 'A' { Enable-AzureDeploy } 'F' { Enable-FunctionApp } 'U' { Write-Host "Checking for updated versions...." $latestVersion = (Find-Module Microsoft.PowerPlatform.DevOps -Repository "PSGallery").Version if ($latestVersion -gt $global:devops_version) { Update-Module Microsoft.PowerPlatform.DevOps Write-Host "Updated to $latestVersion, please restart the tool for it to take effect." } } 'O' { Invoke-OpenSolution } 'C' { Show-ConfigMenu } } Write-Host "" } until ($selection -eq 'q') Clear-Variable devops_* -Scope Global Clear-Host return #Stop-Process -Id $PID } Export-ModuleMember -Function 'Invoke-PowerPlatformDevOps' -Alias ppdo New-Alias -Name ppdo -Value Invoke-PowerPlatformDevOps -Scope Global #, 'Add-CICDEnvironment', 'Add-Solution', 'Connect-AzureDevOps', 'Install-PreReqs' |