Private/_MainFunctions.ps1

function Get-JwtTokenClaimsForPA {
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [string]$JwtToken
    )

    $tokenSplit = $JwtToken.Split(".")
    $claimsSegment = $tokenSplit[1].Replace(" ", "+").Replace("-", "+");
    
    $mod = $claimsSegment.Length % 4
    if ($mod -gt 0) {
        $paddingCount = 4 - $mod;
        for ($i = 0; $i -lt $paddingCount; $i++) {
            $claimsSegment += "="
        }
    }

    $decodedClaimsSegment = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($claimsSegment))

    return ConvertFrom-Json $decodedClaimsSegment
}

Function Authenticate-PowerPlatform($user) {
    $authBaseUri =
    switch ($Endpoint) {
        "usgovhigh" { "https://login.microsoftonline.us" }
        "dod" { "https://login.microsoftonline.us" }
        default { "https://login.windows.net" }
    };

    [string]$Endpoint = "prod"

    [string]$Audience = "https://management.azure.com/"
    [string]$TenantID = $null
    [string]$CertificateThumbprint = $null
    [string]$ClientSecret = $null
    #[string]$ApplicationId = "1950a258-227b-4e31-a9cf-717495945fc2"

    $azTokenCache = Get-Content $env:USERPROFILE\.azure\accessTokens.json | ConvertFrom-Json
    $authResult = $azTokenCache | Where-Object resource -EQ $Audience | Where-Object userId -eq $user
    if (!$authResult) {
        $getToken = az account get-access-token --resource https://management.azure.com/ -s $global:devops_DataVerseADSubscription
        $azTokenCache = Get-Content $env:USERPROFILE\.azure\accessTokens.json | ConvertFrom-Json
        $authResult = $azTokenCache | Where-Object resource -EQ $Audience | Where-Object userId -eq $user    
    }
    [string]$at = $authResult.AccessToken
    $claims = Get-JwtTokenClaimsForPA -JwtToken $authResult.AccessToken

    $global:currentSession = @{
        loggedIn              = $true;
        idToken               = $authResult.IdToken;
        upn                   = $claims.upn;
        tenantId              = $claims.tid;
        userId                = $claims.oid;
        applicationId         = $claims.appid;
        certificateThumbprint = $CertificateThumbprint;
        clientSecret          = $ClientSecret;
        secureClientSecret    = $SecureClientSecret;
        refreshToken          = $authResult.RefreshToken;
        expiresOn             = (Get-Date).AddHours(8);
        resourceTokens        = @{
            $Audience = @{
                accessToken = $authResult.AccessToken;
                expiresOn   = $authResult.ExpiresOn;
            }
        };
        selectedEnvironment   = "~default";
        authBaseUri           = $authBaseUri;
        flowEndpoint          = 
        switch ($Endpoint) {
            "prod" { "api.flow.microsoft.com" }
            "usgov" { "gov.api.flow.microsoft.us" }
            "usgovhigh" { "high.api.flow.microsoft.us" }
            "dod" { "api.flow.appsplatform.us" }
            "china" { "api.powerautomate.cn" }
            "preview" { "preview.api.flow.microsoft.com" }
            "tip1" { "tip1.api.flow.microsoft.com" }
            "tip2" { "tip2.api.flow.microsoft.com" }
            default { throw "Unsupported endpoint '$Endpoint'" }
        };
        powerAppsEndpoint     = 
        switch ($Endpoint) {
            "prod" { "api.powerapps.com" }
            "usgov" { "gov.api.powerapps.us" }
            "usgovhigh" { "high.api.powerapps.us" }
            "dod" { "api.apps.appsplatform.us" }
            "china" { "api.powerapps.cn" }
            "preview" { "preview.api.powerapps.com" }
            "tip1" { "tip1.api.powerapps.com" }
            "tip2" { "tip2.api.powerapps.com" }
            default { throw "Unsupported endpoint '$Endpoint'" }
        };            
        bapEndpoint           = 
        switch ($Endpoint) {
            "prod" { "api.bap.microsoft.com" }
            "usgov" { "gov.api.bap.microsoft.us" }
            "usgovhigh" { "high.api.bap.microsoft.us" }
            "dod" { "api.bap.appsplatform.us" }
            "china" { "api.bap.partner.microsoftonline.cn" }
            "preview" { "preview.api.bap.microsoft.com" }
            "tip1" { "tip1.api.bap.microsoft.com" }
            "tip2" { "tip2.api.bap.microsoft.com" }
            default { throw "Unsupported endpoint '$Endpoint'" }
        };      
        graphEndpoint         = 
        switch ($Endpoint) {
            "prod" { "graph.windows.net" }
            "usgov" { "graph.windows.net" }
            "usgovhigh" { "graph.windows.net" }
            "dod" { "graph.windows.net" }
            "china" { "graph.windows.net" }
            "preview" { "graph.windows.net" }
            "tip1" { "graph.windows.net" }
            "tip2" { "graph.windows.net" }
            default { throw "Unsupported endpoint '$Endpoint'" }
        };
        cdsOneEndpoint        = 
        switch ($Endpoint) {
            "prod" { "api.cds.microsoft.com" }
            "usgov" { "gov.api.cds.microsoft.us" }
            "usgovhigh" { "high.api.cds.microsoft.us" }
            "dod" { "dod.gov.api.cds.microsoft.us" }
            "preview" { "preview.api.cds.microsoft.com" }
            "tip1" { "tip1.api.cds.microsoft.com" }
            "tip2" { "tip2.api.cds.microsoft.com" }
            default { throw "Unsupported endpoint '$Endpoint'" }
        };
    };
}

function Add-ClientSecrettoKeyVault() {    
    $keyName = "$($global:devops_gitRepo)-clientsecret"
    Write-Host "Adding Client Secret to KeyVault with name $keyName"
    $global:devops_projectFile.ClientSecretAKVName = $keyName
    $kvsuccess = az keyvault secret set --name $keyName --vault-name $global:devops_AzureKeyVault --value $global:clientSecret --subscription $global:devops_selectedSubscription 
    Write-Host "Removing Client Secret from Local Store"
    $global:devops_configFile.Projects[$global:devops_projectConfigID].ClientSecret = $null
    $global:devops_configFile | ConvertTo-Json | Set-Content ("$env:APPDATA\Microsoft.PowerPlatform.DevOps\devopsConfig.json")        
    pause    
}

Function Configure-AzureKeyVault() {
    [array]$options = "[Go Back]", "Connect to Existing KeyVault", "Create New KeyVault"
    do {
        $sel = Invoke-Menu -MenuTitle "---- Configure Azure KeyVault ------" -MenuOptions $options  
        $akvSelection = $sel         
    } until ($akvSelection -ge 0)
    switch ($akvSelection) {
        '0' { Show-ConfigMenu }
        '1' {
            Write-Host "Connecting to Existing KeyVault"
            if (!$global:devops_selectedSubscription) {
                Display-AzureAccounts
            }

            [array]$kvList = az keyvault list --subscription $global:devops_selectedSubscription --query [].name --output json | ConvertFrom-Json | Sort-Object
            if (!$kvList) {
                Write-Host "No KeyVaults exist for the Current Subscription, please create one"
                pause
            }
            else {
                do {
                    $sel = Invoke-Menu -MenuTitle "---- Select Azure KeyVault ------" -MenuOptions $kvlist  
                    $akvToUse = $sel         
                } until ($akvToUse -ge 0)
                $global:devops_AzureKeyVault = $kvList[$sel]
                $global:clientSecret = ($global:devops_configFile.Projects[$global:devops_projectConfigID].ClientSecret) | ConvertTo-SecureString
                if ($global:clientSecret) {
                    Add-ClientSecrettoKeyVault
                }
                $global:devops_projectFile.selectedSubscription = $global:devops_selectedSubscription
                $global:devops_projectFile.selectedSubscriptionName = $global:devops_selectedSubscriptionName
                $global:devops_projectFile.AzureKeyVaultName = $global:devops_AzureKeyVault
                $global:devops_projectFile | ConvertTo-Json | Set-Content ("$global:devops_projectLocation\$global:devops_gitRepo.json")                      
            }            
            Show-ConfigMenu
        }
        '2' {
            Write-Host "Create New KeyVault"
            if (!$global:devops_selectedSubscription) {
                Display-AzureAccounts
            }
            Write-Host ""
            $subconfirm = Read-Host "A New Azure KeyVault will be created in the $global:devops_selectedSubscriptionName [Press Enter to Continue or [Q] to quit]"
            if ($subconfirm -eq 'Q') {
                Show-ConfigMenu
            }
            try {
                az account set --subscription $global:devops_selectedSubscription
                [array]$locationlist = az account list-locations --query [].name --output table | Sort-Object 
                do {
                    $sel = Invoke-Menu -MenuTitle "---- Select location for the KeyVault ------" -MenuOptions $locationlist  
                    $locToUse = $sel         
                } until ($locToUse -ge 0)
                $location = $locationlist[$sel]
                $resourcegroup = Read-Host "Enter the Resource Group to add the KeyVault to"
                $kvName = Read-Host "Enter the name for the KeyVault"
                $rgExists = az group list --query "[?name=='$resourcegroup']" | ConvertFrom-Json
                if (!$rgExists) {
                    Write-Host "Resource group does not Exist... Creating"
                    $rg = az group create --location $location --name $resourcegroup
                }
                $createdKV = az keyvault create --location $location --resource-group $resourcegroup --name $kvName
                $global:devops_AzureKeyVault = $kvName
                $global:clientSecret = ($global:devops_configFile.Projects[$global:devops_projectConfigID].ClientSecret) | ConvertTo-SecureString
                if ($global:clientSecret) {
                    Add-ClientSecrettoKeyVault
                }
                $global:devops_projectFile.selectedSubscription = $global:devops_selectedSubscription
                $global:devops_projectFile.selectedSubscriptionName = $global:devops_selectedSubscriptionName
                $global:devops_projectFile.AzureKeyVaultName = $global:devops_AzureKeyVault
                $global:devops_projectFile | ConvertTo-Json | Set-Content ("$global:devops_projectLocation\$global:devops_gitRepo.json")  
        
            }
            catch {
                Write-Host $_
                pause
            }            
            Show-ConfigMenu
        }
        Default {}
    }
}

function Configure-ServicePrincipal() {
    [array]$options = "[Go Back]", "Select Existing Service Principal", "Create New Service Principal"
    do {
        $sel = Invoke-Menu -MenuTitle "---- Configure Service Principal ------" -MenuOptions $options  
        $spSelection = $sel         
    } until ($spSelection -ge 0)
    switch ($spSelection) {
        '0' { Show-ConfigMenu }
        '1' {
            Write-Host "Select Existing Service Principal"
            if (!$global:devops_selectedSubscription) {
                Display-AzureAccounts
            }
            Write-Host "Retrieving available Service Principals.... please wait"
            [array]$spList = az ad sp list --all --query "[?signInAudience=='AzureADMyOrg'].{Name:displayName, ID:appId, Tenant:appOwnerTenantId} " --output json | ConvertFrom-Json | Sort-Object
            if (!$spList) {
                Write-Host "No Service Principals exist for the Current Subscription, please create one"
                pause
            }
            else {
                [array]$spmenu = $spList | ForEach-Object { $_.Name }
                do {
                    $sel = Invoke-Menu -MenuTitle "---- Select Service Principal ------" -MenuOptions $spmenu  
                    $spToUse = $sel         
                } until ($spToUse -ge 0)
                $global:devops_ClientID = $spList[$sel].ID
                $global:devops_TenantID = $spList[$sel].Tenant
                $global:devops_projectFile.ClientID = $global:devops_ClientID
                $global:devops_projectFile.TenantID = $global:devops_TenantID
                $CSecret = Read-Host "Enter the Client Secret for $global:devops_ClientID (or leave blank to create a new one)"
                if (!$CSecret) {
                    Write-Host "No Secret provided .... creating new one"
                    $newCreds = az ad sp credential reset --name $global:devops_ClientID --append --credential-description "Devops" | ConvertFrom-Json
                    $SS = ConvertTo-SecureString -AsPlainText -String $newCreds.password -Force | ConvertFrom-SecureString
                    $updatedProject = [ordered]@{ID = $global:devops_configFile.Projects[$global:devops_projectConfigID].ID; Name = $global:devops_configFile.Projects[$global:devops_projectConfigID].Name; ProjectLocation = $global:devops_projectLocation; ClientSecret = $SS; Tenant = $newCred.tenant }
                    $global:devops_configFile.Projects[$global:devops_projectConfigID] = $updatedProject
                    if ($global:devops_AzureKeyVault) {
                        $global:clientSecret = $newCreds.password
                        Add-ClientSecrettoKeyVault
                    }
                    $global:devops_configFile | ConvertTo-Json | Set-Content ("$env:APPDATA\Microsoft.PowerPlatform.DevOps\devopsConfig.json")        
                }
                else {
                    $SS = ConvertTo-SecureString -AsPlainText -String $CSecret -Force | ConvertFrom-SecureString
                    $updatedProject = [ordered]@{ID = $global:devops_configFile.Projects[$global:devops_projectConfigID].ID; Name = $global:devops_configFile.Projects[$global:devops_projectConfigID].Name; ProjectLocation = $global:devops_projectLocation; ClientSecret = $SS }
                    $global:devops_configFile.Projects[$global:devops_projectConfigID] = $updatedProject
                    if ($global:devops_AzureKeyVault) {
                        $global:clientSecret = $CSecret
                        Add-ClientSecrettoKeyVault
                    }
                    $global:devops_configFile | ConvertTo-Json | Set-Content ("$env:APPDATA\Microsoft.PowerPlatform.DevOps\devopsConfig.json")        
 
                }
                $global:devops_projectFile | ConvertTo-Json | Set-Content ("$global:devops_projectLocation\$global:devops_gitRepo.json")                      
            }            
            Show-ConfigMenu
        }
        '2' {
            Write-Host "Create New Service Principal"
            if (!$global:devops_selectedSubscription) {
                Display-AzureAccounts
            }
            Write-Host ""
            $subconfirm = Read-Host "A New Azure KeyVault will be created in the $global:devops_selectedSubscriptionName [Press Enter to Continue or [Q] to quit]"
            if ($subconfirm -eq 'Q') {
                Show-ConfigMenu
            }
            try {
                az account set --subscription $global:devops_selectedSubscription
                [array]$locationlist = az account list-locations --query [].name --output table | Sort-Object 
                do {
                    $sel = Invoke-Menu -MenuTitle "---- Select location for the KeyVault ------" -MenuOptions $locationlist  
                    $locToUse = $sel         
                } until ($locToUse -ge 0)
                $location = $locationlist[$sel]
                $resourcegroup = Read-Host "Enter the Resource Group to add the KeyVault to"
                $kvName = Read-Host "Enter the name for the KeyVault"
                $rgExists = az group list --query "[?name=='$resourcegroup']" | ConvertFrom-Json
                if (!$rgExists) {
                    Write-Host "Resource group does not Exist... Creating"
                    $rg = az group create --location $location --name $resourcegroup
                }
                $createdKV = az keyvault create --location $location --resource-group $resourcegroup --name $kvName
                $global:devops_AzureKeyVault = $kvName
                $global:devops_projectFile.selectedSubscription = $global:devops_selectedSubscription
                $global:devops_projectFile.selectedSubscriptionName = $global:devops_selectedSubscriptionName
                $global:devops_projectFile.AzureKeyVaultName = $global:devops_AzureKeyVault
                $global:devops_projectFile | ConvertTo-Json | Set-Content ("$global:devops_projectLocation\$global:devops_gitRepo.json")  
        
            }
            catch {
                Write-Host $_
                pause
            }
            pause
            Show-ConfigMenu
        }
        Default {}
    }
}

function Set-ColourOption(
    [string]$value
) {
    if ($value -eq "False") { 
        return "White" 
    } 
    elseif ($value -eq "Error") {
        return "Red"
    }
    elseif ($value -eq "Optional") {
        return "Magenta"
    }
    else { return "Green" }
}

Function Display-AzureAccounts() {
    $AZAccounts = az account list | ConvertFrom-Json
    [array]$options = "Login to a New Account"
    $options += $AZAccounts | ForEach-Object { "$($_.name) $($_.user.name)" }

    do {
        $sel = Invoke-Menu -MenuTitle "---- Please Select your Subscription ------" -MenuOptions $options          
    } until ($sel -ge 0)
    if ($sel -eq 0) {
        az login --use-device-code --allow-no-subscriptions
        #Get Azure Accounts / Subscriptions
        $AZAccounts = az account list | ConvertFrom-Json
        Display-AzureAccounts
    }
    else {
        $global:devops_selectedSubscription = $AZAccounts[$sel - 1].id
        $global:devops_selectedSubscriptionName = $AZAccounts[$sel - 1].name
    }
}

function Update-ProjectFile() {
    $projectFileMaster = Get-Content (Join-Path (Get-Module -Name microsoft.powerplatform.devops).Path -ChildPath ..\Private\emptyProject.json) | ConvertFrom-Json
    if ($projectFileMaster) {
        $properties = Get-Member -InputObject $global:devops_projectFile -MemberType Properties
        $properties | ForEach-Object { $projectFileMaster.$($_.Name) = $global:devops_projectFile.$($_.Name) }
        $global:devops_projectFile = $projectFileMaster
        $global:devops_projectFile | ConvertTo-Json | Set-Content ("$global:devops_projectLocation\$global:devops_gitRepo.json")              
    }
}

Function Display-AzureLogins() {
    [array]$options = "Login to a New Account"
    $AZCreds = az account list --query '[].{Name:user.name, Type:user.type, ID:id, Subscription:name}' --output json | ConvertFrom-Json
    $UniqueCreds = $AZCreds | Sort-Object -Property * -Unique 
    $options += $UniqueCreds | ForEach-Object { "$($_.Name) ($($_.Type))($($_.Subscription))" }

    do {
        $sel = Invoke-Menu -MenuTitle "---- Please Select your AAD Subscription ------" -MenuOptions $options   
    } until ($sel -ge 0)
    if ($sel -eq 0) {
        az login --use-device-code --allow-no-subscriptions
        Display-AzureLogins
    }
    else {
        $global:devops_DataverseEmail = $UniqueCreds[$sel - 1].Name
        $global:devops_DataVerseADSubscription = $UniqueCreds[$sel - 1].id
        $global:devops_DataverseCredType = $UniqueCreds[$sel - 1].Type
    }
}

function Format-Json {
    <#
  .SYNOPSIS
      Prettifies JSON output.
  .DESCRIPTION
      Reformats a JSON string so the output looks better than what ConvertTo-Json outputs.
  .PARAMETER Json
      Required: [string] The JSON text to prettify.
  .PARAMETER Minify
      Optional: Returns the json string compressed.
  .PARAMETER Indentation
      Optional: The number of spaces (1..1024) to use for indentation. Defaults to 4.
  .PARAMETER AsArray
      Optional: If set, the output will be in the form of a string array, otherwise a single string is output.
  .EXAMPLE
      $json | ConvertTo-Json | Format-Json -Indentation 2
  #>

    [CmdletBinding(DefaultParameterSetName = 'Prettify')]
    Param(
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        [string]$Json,

        [Parameter(ParameterSetName = 'Minify')]
        [switch]$Minify,

        [Parameter(ParameterSetName = 'Prettify')]
        [ValidateRange(1, 1024)]
        [int]$Indentation = 4,

        [Parameter(ParameterSetName = 'Prettify')]
        [switch]$AsArray
    )

    if ($PSCmdlet.ParameterSetName -eq 'Minify') {
        return ($Json | ConvertFrom-Json) | ConvertTo-Json -Depth 100 -Compress
    }

    # If the input JSON text has been created with ConvertTo-Json -Compress
    # then we first need to reconvert it without compression
    if ($Json -notmatch '\r?\n') {
        $Json = ($Json | ConvertFrom-Json) | ConvertTo-Json -Depth 100
    }

    $indent = 0
    $regexUnlessQuoted = '(?=([^"]*"[^"]*")*[^"]*$)'

    $result = $Json -split '\r?\n' |
    ForEach-Object {
        # If the line contains a ] or } character,
        # we need to decrement the indentation level unless it is inside quotes.
        if ($_ -match "[}\]]$regexUnlessQuoted") {
            $indent = [Math]::Max($indent - $Indentation, 0)
        }

        # Replace all colon-space combinations by ": " unless it is inside quotes.
        $line = (' ' * $indent) + ($_.TrimStart() -replace ":\s+$regexUnlessQuoted", ': ')

        # If the line contains a [ or { character,
        # we need to increment the indentation level unless it is inside quotes.
        if ($_ -match "[\{\[]$regexUnlessQuoted") {
            $indent += $Indentation
        }

        $line
    }

    if ($AsArray) { return $result }
    return $result -Join [Environment]::NewLine
}

Function Invoke-Menu() {    
    Param(
        [Parameter(Mandatory = $True)][String]$MenuTitle,
        [Parameter(Mandatory = $True)][array]$MenuOptions
    )
    $MaxValue = $MenuOptions.count - 1
    $Selection = 0
    $EnterPressed = $False
    
    Clear-Host

    While ($EnterPressed -eq $False) { 
        Write-Host "$MenuTitle"
        For ($i = 0; $i -le $MaxValue; $i++) {            
            If ($i -eq $Selection) {
                Write-Host -BackgroundColor Cyan -ForegroundColor Black "[ $($MenuOptions[$i]) ]"
            }
            Else {
                Write-Host " $($MenuOptions[$i]) "
            }
        }
        $KeyInput = $host.ui.rawui.readkey("NoEcho,IncludeKeyDown").virtualkeycode
        Switch ($KeyInput) {
            13 {
                $EnterPressed = $True
                Return $Selection
                Clear-Host
                break
            }
            38 {
                If ($Selection -eq 0) {
                    $Selection = $MaxValue
                }
                Else {
                    $Selection -= 1
                }
                Clear-Host
                break
            }
            40 {
                If ($Selection -eq $MaxValue) {
                    $Selection = 0
                }
                Else {
                    $Selection += 1
                }
                Clear-Host
                break
            }
            Default {
                Clear-Host
            }
        }
    }
}

function Copy-GitRepoFromTemplate {
    try {
        if ($global:devops_Core) {
            $path = Read-Host "Enter a folder to create a new git Repository for your Power Platform Project"
        }
        else {
            Write-Host "Select a folder to create a new git Repository for your Power Platform Project"
            $application = New-Object -ComObject Shell.Application
            $path = ($application.BrowseForFolder(0, 'Select a folder', 0)).Self.Path
    
        }
        $global:devops_projectLocation = "$path\$global:devops_gitRepo"
    
        if (!(Test-Path -Path "$global:devops_projectLocation") -and !($null -eq $path)) {
            Write-Host "Creating Project Folder"
            New-Item -Path $path -Name $global:devops_gitRepo -ItemType Directory
            Copy-Item -Path (Join-Path $PSScriptRoot emptyProject.json) -Destination $global:devops_projectLocation\$global:devops_gitRepo.json
            $newProject = @([ordered]@{ID = $global:devops_configFile.Projects.Count; Name = $global:devops_gitRepo; ProjectLocation = "$global:devops_projectLocation" })
            $global:devops_configFile.Projects += $newProject
            $global:devops_configFile | ConvertTo-Json | Set-Content ("$env:APPDATA\Microsoft.PowerPlatform.DevOps\devopsConfig.json")
            $global:devops_projectFile = Get-Content ("$global:devops_projectLocation\$global:devops_gitRepo.json") | ConvertFrom-Json
            Update-ProjectFile
            Set-Location -Path "$global:devops_projectLocation" 
              
    
            $message = "Setting up PowerPlatform.DevOps locally"
            Write-Host $message
    
            Remove-Item -Path (Join-Path $PSScriptRoot ..\FrameworkTemplate\node_modules) -Recurse -Force -ErrorAction Ignore  
            Copy-Item -Path (Join-Path $PSScriptRoot ..\FrameworkTemplate\*) -Destination $global:devops_projectLocation\ -Recurse -Force
        
            $message = "Confirming Git User Details"
            Write-Host $message
    
            $GitUser = git config --global user.name
            $GitEmail = git config --global user.email
    
            If ($null -eq $GitUser) {
                $GitUser = Read-Host "Enter your name (to use when committing changes to Git)"
                git config --global user.name $GitUser
            }
    
            If ($null -eq $GitEmail) {
                $GitEmail = Read-Host "Enter your email address (to use when committing changes to Git)"
                git config --global user.email $GitEmail
            }  
    
            Remove-Item .git -Recurse -Force -ErrorAction SilentlyContinue
    
            git init

            Write-Host "Updating Build.yaml ..."
            (Get-Content -Path $global:devops_projectLocation\build.yaml) -replace "replaceRepo", $global:devops_gitRepo | Set-Content -Path $global:devops_projectLocation\build.yaml

            Write-Host "Rename PowerPlatformDevOps.sln to $global:devops_gitRepo.sln"
            Rename-Item -Path $global:devops_projectLocation\PowerPlatformDevOps.sln -NewName "$global:devops_gitRepo.sln"

            Write-Host "Rename dot files"
            Rename-Item -Path $global:devops_projectLocation\file.gitignore -NewName ".gitignore"
            Rename-Item -Path $global:devops_projectLocation\file.artifactignore -NewName ".artifactignore"
            Rename-Item -Path $global:devops_projectLocation\file.gitattributes -NewName ".gitattributes"

            $global:devops_projectFile.gitRepo = $global:devops_gitRepo
            $global:devops_projectFile | ConvertTo-Json | Set-Content ("$global:devops_projectLocation\$global:devops_gitRepo.json") 
    
        }
        else {
            Write-Warning "The Path $global:devops_projectLocation already exists, please select a different path or project name"
            $continue = Read-Host -Prompt "Press [Enter] to Continue or [Q] to Quit"
            if (!$continue -eq 'Q') {
            }
        } 
    }
    catch {
    
    }
}

function Add-GitRepo {
    do {
        $global:devops_gitRepo = Read-Host -Prompt "Please enter a Name for the Git Repository you wish to Create"
    }until ($global:devops_gitRepo -ne "")
  
    if (!(Test-Path $env:APPDATA\Microsoft.PowerPlatform.DevOps)) {
        New-Item -Path $env:APPDATA\Microsoft.PowerPlatform.Devops -ItemType Directory
    }
    if (!($global:devops_configFile.PSobject.Properties.Project -contains "Name") -or !($global:devops_configFile.Projects.Name.Contains($global:devops_gitRepo))) {
        Copy-GitRepoFromTemplate
        pause
    }
    else {
        Write-Host "You already have a repo called $global:devops_gitRepo, please select a different name"
        $global:devops_gitRepo = ""
        Add-GitRepo
    }
}

function Select-GitRepo {
    if ($global:devops_configFile.Projects.Count -gt 0) {
        [array]$options = "Go Back"
        $options += $global:devops_configFile.Projects | ForEach-Object { "$($_.Name)" }

        do {
            $thesel = Invoke-Menu -MenuTitle "---- Please Select your Project ------" -MenuOptions $options
            $sel = $thesel - 1
            $selectedRepo = $global:devops_configFile.Projects[$sel].Name
            $global:devops_projectConfigID = $sel
        } until ($selectedRepo -ne "")
        if ($thesel -eq 0) {
            return
        }
        if (($global:devops_configFile.Projects[$sel].PSobject.Properties.Name -contains "ProjectLocation")) {
            $global:devops_projectFile = Get-Content ("$($global:devops_configFile.Projects[$sel].ProjectLocation)\$selectedRepo.json") | ConvertFrom-Json
            Update-ProjectFile
            $global:devops_projectLocation = $global:devops_configFile.Projects[$sel].ProjectLocation
        }
        else {
            $global:devops_projectFile = Get-Content ("$env:APPDATA\Microsoft.PowerPlatform.Devops\$selectedRepo\$selectedRepo.json") | ConvertFrom-Json
            $global:devops_projectLocation = $global:devops_projectFile.ProjectLocation
            if (! (Test-Path -Path "$global:devops_projectLocation\$selectedRepo.json")) {
                Copy-Item -Path "$env:APPDATA\Microsoft.PowerPlatform.Devops\$selectedRepo\$selectedRepo.json" -Destination " $global:devops_projectLocation\"            
            }
            $updatedProject = [ordered]@{ID = $global:devops_configFile.Projects[$sel].ID; Name = $global:devops_configFile.Projects[$sel].Name; ProjectLocation = $global:devops_projectLocation }
            $global:devops_configFile.Projects[$sel] = $updatedProject
            $global:devops_configFile | ConvertTo-Json | Set-Content ("$env:APPDATA\Microsoft.PowerPlatform.DevOps\devopsConfig.json")
            $global:devops_projectFile = Get-Content ("$global:devops_projectLocation\$selectedRepo.json") | ConvertFrom-Json
            Update-ProjectFile
        }
         
        Set-Location  $global:devops_projectLocation
        $global:devops_projectTitle = $global:devops_projectFile.gitRepo
        $global:devops_gitRepo = $global:devops_projectFile.gitRepo     
        $global:devops_selectedSubscriptionName = $global:devops_projectFile.selectedSubscriptionName
        $global:devops_selectedSubscription = $global:devops_projectFile.selectedSubscription
        $global:devops_AzureKeyVault = $global:devops_projectFile.AzureKeyVaultName
        $global:devops_ClientID = $global:devops_projectFile.ClientID
        $global:devops_TenantID = $global:devops_projectFile.TenantID
        $global:devops_EnvironmentSecurityID = $global:devops_projectFile.EnvironmentSecurityID
    }
    else {
        Write-Host "You don't have any Projects set up, please Add a new one or Clone an existing Repo"
        pause
    }

}

function Clone-GitRepo {
    $regEx = '(http[s]?|[s]?ftp[s]?)(:\/\/)([^\s,]+)'

    do {
        $repoURL = Read-Host "Please enter the git clone URL"
    } until ($repoURL -ne "" -and $repoURL -match $regEx)  

    if ($global:devops_Core) {
        $path = Read-Host "Please enter the local path to Clone to"
    }
    else {
        $application = New-Object -ComObject Shell.Application
        $path = ($application.BrowseForFolder(0, "Select a folder to Clone your $selectedRepo Repo to", 0)).Self.Path    
    }

    $repoFinder = $repoURL -split "/"
    $selectedRepo = $repoFinder[$repoFinder.Count - 1].Replace('.git', '')
    $global:devops_gitRepo = $selectedRepo
    Write-Host $selectedRepo

    Write-host $path
    Write-Host $repoURL

    $isADO = $repoURL.Contains("azure.com") -or $repoURL.Contains("visualstudio.com")

    if ($isADO) {
        $AZAccounts = az account list | ConvertFrom-Json
        if (!($global:devops_selectedSubscription -ne "" -and (($AZAccounts | ForEach-Object { $_.id }) -contains $global:devops_selectedSubscription))) {
            if ($AZAccounts.Length -eq 0) {
                Write-Host "No Accounts Found, please login"
                az login --use-device-code --allow-no-subscriptions        
                $AZAccounts = az account list | ConvertFrom-Json
                Display-AzureAccounts
            }
            else {
                Display-AzureAccounts
            }        
        }
        az account set --subscription $global:devops_selectedSubscription
        #Get Access Token
        $azureDevopsResourceId = "499b84ac-1321-427f-aa17-267ca6975798"
        $token = az account get-access-token --resource $azureDevopsResourceId | ConvertFrom-Json
        $authValue = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes(":" + $token.accessToken))  

        . git -c http.$repoURL.extraHeader="Authorization: Basic $authValue" clone $repoURL $path\$global:devops_gitRepo    

    }
    else {
        . git clone $repoURL $path\$global:devops_gitRepo
    }
    
    $global:devops_projectLocation = "$path\$selectedRepo"

    if (!(Test-Path -Path "$global:devops_projectLocation\$selectedRepo.json")) {
        Copy-Item -Path (Join-Path $PSScriptRoot emptyProject.json) -Destination $global:devops_projectLocation\$selectedRepo.json
        $global:devops_projectFile = Get-Content ("$global:devops_projectLocation\$selectedRepo.json") | ConvertFrom-Json
        Update-ProjectFile
        $global:devops_projectFile.gitRepo = $global:devops_gitRepo
        $global:devops_projectFile | ConvertTo-Json | Set-Content ("$global:devops_projectLocation\$global:devops_gitRepo.json")            
    }
    $newProject = [ordered]@{ID = $global:devops_configFile.Projects.Count; Name = $selectedRepo; ProjectLocation = $global:devops_projectLocation }
    $global:devops_configFile.Projects += $newProject
    $global:devops_configFile | ConvertTo-Json | Set-Content ("$env:APPDATA\Microsoft.PowerPlatform.DevOps\devopsConfig.json")
    $global:devops_projectFile = Get-Content ("$global:devops_projectLocation\$global:devops_gitRepo.json") | ConvertFrom-Json
    Update-ProjectFile
     
    Set-Location  $global:devops_projectLocation
    $global:devops_projectTitle = $global:devops_projectFile.gitRepo

    git config http.$repoURL.extraHeader "Authorization:Basic $authValue"

    pause
}

function Get-GitRepo {
    if (!(Test-Path $env:APPDATA\Microsoft.PowerPlatform.DevOps)) {
        New-Item -Path $env:APPDATA\Microsoft.PowerPlatform.Devops -ItemType Directory
    }

    if ($global:devops_Core) {
        $path = Read-Host "Please enter the location of your Existing Project"
    }
    else {
        $application = New-Object -ComObject Shell.Application
        $path = ($application.BrowseForFolder(0, "Select the location of your Existing Project", 0)).Self.Path           
    }

    $repoFinder = $path -split "\\"
    $selectedRepo = $repoFinder[$repoFinder.Count - 1]
    Write-Host $selectedRepo
    Write-Host $path\$selectedRepo.json
    
    if ((Test-Path -Path "$path\$selectedRepo.json")) {
        $global:devops_projectLocation = "$path"

        $global:devops_projectFile = Get-Content ("$path\$selectedRepo.json") | ConvertFrom-Json
        Update-ProjectFile
        $newProject = [ordered]@{ID = $global:devops_configFile.Projects.Count; Name = $selectedRepo; ProjectLocation = $global:devops_projectLocation }
        $global:devops_configFile.Projects += $newProject
        $global:devops_configFile | ConvertTo-Json | Set-Content ("$env:APPDATA\Microsoft.PowerPlatform.DevOps\devopsConfig.json")
        $global:devops_gitRepo = $global:devops_projectFile.gitRepo
        Set-Location  $global:devops_projectLocation
    }
    else {
        Write-Host "$path is not a valid Project Location, please Add a new one or Clone an existing Repo"
        pause
    }

}