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 }
    }
}

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"
}
$global: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:version = $getVersion.Replace("'", "")
$global:projectFile = "False"
$global:gitRepo = ""
$global:projectLocation = ""
$global:continue = $true
$global:devMode = $false

if ($global:projectFile -eq "False") {
    $global:projectTitle = "(No Project Selected)"
}
else {
    $global:projectTitle = $global:projectFile.gitRepo
}

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


function Install-PreReqs {
    $message = "Checking Pre-requisites"
    Write-Host $message

    try {
        Invoke-InstallPreRequisites
        $global:configfile.PreReqsComplete = "True"
    }
    catch {
        $global:configfile.PreReqsComplete = "Error"
        pause
    }
    $global: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:projectFile.ADOConfigured = "True"
    }
    catch {
        $global:projectFile.ADOConfigured = "Error"
        pause
    }
    $global:projectFile | ConvertTo-Json | Set-Content ("$global:projectLocation\$global:gitRepo.json")  
   
}

function Add-Solution {
    $message = "Adding D365 / CDS Solution"
    Write-Host $message

    try {
        Invoke-AddSolution
        $global:projectFile.SolutionAdded = "True"
    }
    catch {
        $global:projectFile.SolutionAdded = "Error"
        pause
    }
    $global:projectFile | ConvertTo-Json | Set-Content ("$global:projectLocation\$global:gitRepo.json") 
   
}

function Add-CICDEnvironment {
    $message = "Configuring CI/CD Environment"
    Write-Host $message

    try {
        Invoke-ConfigureCICD
    }
    catch {
        $global:projectFile.CICDEnvironmentName = "Error"
        pause
    }
    $global:projectFile | ConvertTo-Json | Set-Content ("$global:projectLocation\$global:gitRepo.json") 
   
}

function Add-Project {
    if ($global:configfile.Projects.Count -gt 1 -or $global: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:projectFile.SolutionAdded = "Error"
        pause
    }
    $global:projectFile | ConvertTo-Json | Set-Content ("$global:projectLocation\$global:gitRepo.json") 
    $global:configfile | ConvertTo-Json | Set-Content ("$env:APPDATA\Microsoft.PowerPlatform.DevOps\devopsConfig.json")
   
}

function Export-Solution {
    if ($global:projectFile.CDSSolutions.Count -gt 0) {
        $options = $global:projectFile.CDSSolutions | ForEach-Object { $_.SolutionName }

        do {
            $sel = Invoke-Menu -MenuTitle "---- Please Select the Solution to Export and Unpack ------" -MenuOptions $options
            $selectedSolution = $global:projectFile.CDSSolutions[$sel].SolutionName
        } until ($selectedSolution -ne "")

        Write-Host $selectedSolution
        Write-Host $global:projectLocation
        Set-Location -Path "$global:projectLocation\$selectedSolution\Scripts"
        if ($global:projectFile.ADOConfigured -eq "True") {
            try {
                git pull origin
            }
            catch {
                pause
            }            
        }
        & .\SolutionExport.ps1 -DevMode $global:devMode
        Set-Location -Path $global:projectLocation
    }
}

function Sync-Solution {
    $commitMessage = Read-Host "Enter a description for your Commit"
    git add -A
    git commit -m $commitMessage    
    if ($global:projectFile.ADOConfigured -eq "True") {
        git push origin
    }
    pause
}

function Show-SolutionChanges {
    git status
    pause
}

function Enable-AzureDeploy {
    if ($global:projectFile.ARMAdded -ne "True") {
        Copy-Item -Path (Join-Path $PSScriptRoot Snippets\AzureResources\.) -Destination $global:projectLocation -Recurse -Force
        $buildYAML = Get-Content -Path "$global:projectLocation\Build.yaml"
        $azureYAML = Get-Content -Path  (Join-Path $PSScriptRoot Snippets\AzureDeploy.yaml)
        $azureYAML = $azureYAML.Replace('replaceRepo', $global:projectFile.gitRepo)
        $buildYAML + $azureYAML | Set-Content -Path  "$global:projectLocation\Build.yaml"

        $azureParams = Get-Content -Path "$global:projectLocation\AzureResources\azuredeploy.parameters.json"
        $azureParams = $azureParams.Replace('AddName', $global:projectFile.gitRepo)
        $azureParams |  Set-Content -Path "$global:projectLocation\AzureResources\azuredeploy.parameters.json"

        $adoOrg = $global:projectFile.ADOOrgName
        $adoProject = $global:projectFile.ADOProject
        $adoRepo = $global: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: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:projectFile.ARMAdded = "True"
        $global:projectFile | ConvertTo-Json | Set-Content ("$global:projectLocation\$global:gitRepo.json") 
        Write-Host "Please make sure you follow the instructions for configuring a Service Connection in Azure DevOps, as detailed in $global: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:projectFile.ARMAdded -eq "True") {
        if ($global:projectFile.FunctionAppAdded -ne "True") {
            Copy-Item -Path (Join-Path $PSScriptRoot Snippets\FunctionApp\.) -Destination $global:projectLocation -Recurse -Force
            dotnet sln $global:gitRepon.sln add FunctionApp\FunctionApp.csproj

            $buildYAML = Get-Content -Path "$global:projectLocation\Build.yaml"
            $azureYAML = Get-Content -Path  (Join-Path $PSScriptRoot Snippets\FunctionAppDeploy.yaml)
            $azureYAML = $azureYAML.Replace('replaceRepo', $global:projectFile.gitRepo)
            $buildYAML + $azureYAML | Set-Content -Path  "$global:projectLocation\Build.yaml"

            $global:projectFile.FunctionAppAdded = "True"
            $global:projectFile | ConvertTo-Json | Set-Content ("$global:projectLocation\$global: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 Show-Menu {
    param (
        [string]$Title = 'Power Platform DevOps'
    )
    $devopsConfigMessage = "(ADO Org : $($global:projectFile.ADOOrgName) | ADO Project : $($global:projectFile.ADOProject) | git Repo : $($global:projectFile.gitRepo))"
    $CICDConfigMessage = "(CI/CD Environment : $($global:projectFile.CICDEnvironmentName) | CI/CD URL : $($global:projectFile.CICDEnvironmentURL))"
    if ($global:projectFile -eq "False") {
        $Title = "Power Platform DevOps (No Project Selected)"
    }
    else {
        $Title = "Power Platform DevOps ($($global:projectFile.gitRepo))"
        $global:projectTitle = $global:projectFile.gitRepo
        Set-Location $global:projectLocation
    }
    $global:message = @"
 
    Welcome to the Power Platform DevOps script.
     
     ver: $global:version (latest available version : $latestVersion)
     project: $global:projectTitle
     repo : $global:gitRepo
     
"@

    [console]::ForegroundColor = "White"
    Clear-Host
    Write-Host $global:logo -ForegroundColor Magenta
    Write-Host $global:message -ForegroundColor White
    $Repeater = "=" * $Title.Length
    Write-Host "================ $Title ================" -ForegroundColor White
    
    Write-Host "1: Run Pre-requisite checks (Install / Update)." -ForegroundColor (Set-ColourOption $global:configfile.PreReqsComplete)
    if (($global:configfile.PreReqsComplete -eq "True")) {
        Write-Host "2: Create a New Project or Select and Existing One" -ForegroundColor (Set-ColourOption $global:projectFile)    
    }    
    if (!($global:projectFile -eq "False") -and ($global:configfile.PreReqsComplete -eq "True")) {
        Write-Host "3: Configure Azure DevOps" $devopsConfigMessage -ForegroundColor (Set-ColourOption $global:projectFile.ADOConfigured)
        Write-Host "4: Add New D365 / CDS Solution." -ForegroundColor (Set-ColourOption $global:projectFile.SolutionAdded)
        if ($global:projectFile.ADOConfigured -eq "True") {
            Write-Host "5: Configure Continuous Deployment" $CICDConfigMessage -ForegroundColor (Set-ColourOption $global:projectFile.CICDEnvironmentName)        
        }
        Write-Host "A: Enable [A]zure Resource Management Deployment." -ForegroundColor (Set-ColourOption $global:projectFile.ARMAdded)
        Write-Host "F: Add Azure [F]unction App Project." -ForegroundColor (Set-ColourOption $global:projectFile.FunctionAppAdded)
        if ($global: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: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:projectFile.CICDEnvironmentName -ne "False") {
                Write-Host "T: Add Additional Deployment Environment [T]arget" -ForegroundColor Cyan
            }
        }
    }
    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:version) {
        Write-Host "There is a newer version available, please run Update-Module Microsoft.PowerPlatform.DevOps to update" -ForegroundColor Yellow
        Write-Host ""
    }
}

function Invoke-PowerPlatformDevOps {
    Param(
        [string] [Parameter(Mandatory = $false)] $PreLoadProjectPath = "",
        [string] [Parameter(Mandatory = $false)] $PreLoadedProjectName = "",
        [string] [Parameter(Mandatory = $false)] $PreSelection = ""
    )

    if ($PreLoadProjectPath -ne "") {
        if ((Test-Path -Path "$PreLoadProjectPath\$PreLoadedProjectName.json"))
        {
            $global:projectLocation = "$PreLoadProjectPath"
    
            $global:projectFile = Get-Content ("$PreLoadProjectPath\$PreLoadedProjectName.json") | ConvertFrom-Json
            $global:gitRepo = $global:projectFile.gitRepo
            Set-Location  $global:projectLocation
        }  
    }
    Write-Host ""
    [console]::ForegroundColor = "White"
    do {
        $global:configFile = Get-Content ("$env:APPDATA\Microsoft.PowerPlatform.DevOps\devopsConfig.json") | ConvertFrom-Json
        if ($global:projectFile -ne "False") {
            Set-Location $global:projectLocation
        }
        Show-Menu

        if ($PreSelection -ne "") {
            $selection = $PreSelection
            $global: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' {
                Invoke-AddEnvironments
            } 'A' {
                Enable-AzureDeploy
            } 'F' {
                Enable-FunctionApp
            } 'U' {
                Write-Host "Checking for updated versions...."
                $latestVersion = (Find-Module Microsoft.PowerPlatform.DevOps -Repository "PSGallery").Version
            }
        }
        Write-Host ""
    }
    until ($selection -eq 'q')
    Stop-Process -Id $PID
}

Export-ModuleMember -Function 'Invoke-PowerPlatformDevOps'
#, 'Add-CICDEnvironment', 'Add-Solution', 'Connect-AzureDevOps', 'Install-PreReqs'