Module/DevOps/Initialize-BCSDevOpsRepository.ps1

<#
.SYNOPSIS
  Initialize a BrightCom Repository
 
.DESCRIPTION
  Updates a BrightCom App Repository with default values
  You will need
 
.PARAMETER projectName
  Name of the project where the repository is.
 
.PARAMETER repositoryName
  Name of the repository to create, if left empty repositoryName will be the same as projectName.
  
.PARAMETER projectNameShort
  Short name for the project 3-4 characters.
  
.PARAMETER appIdRangeFrom
  From ID ranges for the app
  
.PARAMETER appIdRangeTo
  From ID ranges for the app
 
.PARAMETER ciTenants
  Tenant names where the app will be deployed, upgraded and installed when the CI pipeline runs. Divide multiple tenants with comma (e.g. Tenant1, Tenant2). Omit to deploy to global scope
 
.PARAMETER cdTenants
  Tenant names where the app will be deployed, upgraded and installed when the Release pipeline runs. Divide multiple tenants with comma (e.g. Tenant1, Tenant2). Omit to deploy to global scope
 
.PARAMETER templateRepoUrl
  Url to template repository if different from the default repository
 
.PARAMETER sourcePat
  Personal Access Token
 
.EXAMPLE
  Initialize-BCSDevOpsRepository -projectName "MyProjectName" -repositoryName "MyRepositoryName" -projectNameShort "MPN" -appIdRangeFrom "50000" -appIdRangeTo "50099" -ciTenants "MyQATenant" -cdTenants "MyProdTenant" -sourcePat (Get-BCSSecureString -InputString "MyDevOpsPat")
 
.NOTES
    Author: Mathias Stjernfelt
    Website: http://www.brightcom.se
#>


function Initialize-BCSDevOpsRepository {
  Param (
    [Parameter(Mandatory = $true)]
    [string]$projectNameShort,
    [Parameter(Mandatory = $true)]
    [securestring]$sourcePat,
    [Parameter(Mandatory = $false)]
    [string]$organisation = 'BrightComSolutions',
    [Parameter(Mandatory = $false)]
    [string]$projectName = "BrightCom Solutions",
    [Parameter(Mandatory = $false)]
    [string]$repositoryName,
    [Parameter(Mandatory = $false)]
    [string]$appIdRangeFrom,
    [Parameter(Mandatory = $false)]
    [string]$appIdRangeTo,
    [Parameter(Mandatory = $false)]
    [string]$ciTenants,
    [Parameter(Mandatory = $false)]
    [string]$cdTenants,
    [Parameter(Mandatory = $false)]
    [string]$templateRepoUrl,
    [Parameter(Mandatory = $false)]
    [string]$templateBranch = 'main',
    [Parameter(Mandatory = $false)]
    [switch]$localOnly,
    [Parameter(Mandatory = $false)]
    [string]$localOnlyProjectPath,
    [Parameter(Mandatory = $false)]
    [switch]$openInVSCode
  )

    if ([string]::IsNullOrEmpty($ciTenants)) {
      $ciTenants = ""
    }

    if ([string]::IsNullOrEmpty($cdTenants)) {
      $cdTenants = ""
    }

    if ([string]::IsNullOrEmpty($templateRepoUrl)) {
      $templateRepoUrl = "https://BrightComSolutions@dev.azure.com/BrightComSolutions/BrightCom%20Solutions/_git/BCS%20AL%20Project%20Template"
    }

    if ($localOnly) {
      if ([string]::IsNullOrEmpty($localOnlyProjectPath)) {
        throw "Plese provide a path in parameter -localOnlyProjectPath"
      }
    }

    $tempGuid = New-Guid
    $templatePath = "$env:TEMP\$tempGuid\template"
    $oldLocation = Get-Location

    if (-not $localOnly) {
      #Check if repository exists, if not create it.
      $fullOrgUrl = "https://dev.azure.com/$organisation"
      $sourcePatPlainText = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($sourcePat))      
      $sourcePatPlainText | az devops login --org $fullOrgUrl
      $ErrorActionPreference = "silentlyContinue"
      $repoInfo = az repos show --repository $repositoryName --org $fullOrgUrl --project $projectName | ConvertFrom-Json

      if (-not [string]::IsNullOrEmpty($repoInfo)) {
        $question = "Repository $repositoryName already exists in DevOps project $projectName, would you like to delete it?"
        $choices = '&Yes', '&No'
      
        $decision = $Host.UI.PromptForChoice($title, $question, $choices, 1)
        if ($decision -eq 0) {
          Write-Host "Deleting repository $($repoInfo.Name)" -ForegroundColor Green
          az repos delete --id $repoInfo.Id --org $fullOrgUrl --project $projectName --yes
        }
        else {
          throw "Aborted by user"
        }
      }
    }

    #Check if powershell-yaml is installed
    if (-not (Get-Module -ListAvailable -Name "powershell-yaml")) {
      Write-Host "powershell-yaml is not installed. Installing it from Powershell Gallery." -ForegroundColor Green
      Install-Module -Name powershell-yaml -force
    }

    Write-Host "Downloading template files from $templateRepoUrl" -ForegroundColor Green

    if (-not(Test-Path -Path $templatePath)) {
      New-Item -Path $templatePath -ItemType Directory | Out-Null
    }

    Set-Location $templatePath
    & git clone $templateRepoUrl $templatePath
    & git checkout $templateBranch
    
    Set-Location $oldLocation
    
    Remove-Item -Path "$templatePath\.git" -Recurse -Force

    $tempProjectPath = "$env:TEMP\$tempGuid\project"

    if (-not (Test-Path -Path $tempProjectPath)) {
      New-Item -Path $tempProjectPath -ItemType Directory | Out-Null
    }

    if (-not $localOnly) {
      Write-Host "Creating repository $repositoryName in DevOps project $projectName" -ForegroundColor Green
      az repos create --name $repositoryName --org $fullOrgUrl --project $projectName | Out-Null
  
      $repoUrl = [uri]::EscapeUriString("https://$organisation@dev.azure.com/$organisation/$projectName/_git/$repositoryName")

      Write-Host "Downloading target repository" -ForegroundColor Green
      Set-Location $tempProjectPath
      & git clone $repoUrl $tempProjectPath
    }
    else {
      Write-Host "Creating project $projectName locally" -ForegroundColor Green
      New-Item -Path $tempProjectPath -ItemType Directory | Out-Null
    }
    
    Copy-Item -Path "$templatePath\*" -Destination $tempProjectPath -Recurse -ErrorAction Stop
    
    Set-Location "$tempProjectPath" -ErrorAction Stop

    if ($localOnly) {
      & git init -q
    }

    #region UpdateProjectFles
    # Update app\app.json
    $fileName = "App\app.json"
    $filecontent = Get-Content -Path $fileName | ConvertFrom-Json
    $filecontent.id = [guid]::NewGuid();
    $filecontent.name = $repositoryName;
    $filecontent.idRanges[0].from = $appIdRangeFrom;
    $filecontent.idRanges[0].to = $appIdRangeTo;

    Write-Host "Updating App/app.json" -ForegroundColor Green
    Write-Host " id `t`t $($filecontent.id)" -ForegroundColor Yellow
    Write-Host " name `t`t $($filecontent.name)" -ForegroundColor Yellow
    Write-Host " id range `t $appIdRangeFrom..$appIdRangeTo" -ForegroundColor Yellow

    $filecontent | ConvertTo-Json | Format-Json -Indentation 2 | out-file $filename

    #Rename Workspace file
    Write-Host "Renaming VS Code workspace file to $repositoryName.code-workspace" -ForegroundColor Green
    Move-Item -Path "BCS AL Project Template.code-workspace" -Destination "$repositoryName.code-workspace"

    # Update test\app.json
    if (Test-Path -Path "Test\app.json") {
      $fileName = "Test\app.json"
      $filecontent = Get-Content -Path $fileName | ConvertFrom-Json

      $filecontent.id = [guid]::NewGuid();
      $filecontent.name = "$repositoryName - Test";

      Write-Host "Updating Test/app.json" -ForegroundColor Green
      Write-Host " id `t`t $($filecontent.id)" -ForegroundColor Yellow
      Write-Host " name `t`t $($filecontent.name)" -ForegroundColor Yellow

      $filecontent | ConvertTo-Json | Format-Json -Indentation 2 | out-file $filename
    }

    # pte-qa-settings.json
    Write-Host "Updating CI Settings file" -ForegroundColor Green 
    $fileName = ".azureDevOps\pte-qa-settings.json"
    $filecontent = Get-Content -Path $fileName | ConvertFrom-Json
    $filecontent.name = "$projectNameShort";
    
    if ((-not [string]::IsNullOrEmpty($ciTenants)) -and ((-not [string]::IsNullOrEmpty($filecontent.deployments)))) {
      $filecontent.deployments[0].DeployToTenants = $ciTenants.Split(',');
    }  
  
    Write-Host " name `t`t $projectNameShort" -ForegroundColor Yellow
    Write-Host " Tenants `t `"$ciTenants`"" -ForegroundColor Yellow
    $filecontent | ConvertTo-Json -Depth 3 | Format-Json -Indentation 2 | out-file $filename

    # pte-prod-settings.json
    Write-Host "Updating Release Settings file" -ForegroundColor Green
    $fileName = ".azureDevOps\pte-prod-settings.json"
    $filecontent = Get-Content -Path $fileName | ConvertFrom-Json
    $filecontent.name = "$projectNameShort";

        if ((-not [string]::IsNullOrEmpty($ciTenants)) -and ((-not [string]::IsNullOrEmpty($filecontent.deployments)))) {
      $filecontent.deployments[0].DeployToTenants = $cdTenants.Split(',');
    }    
    
    Write-Host " name `t`t $projectNameShort" -ForegroundColor Yellow
    Write-Host " Tenants `t `"$cdTenants`"" -ForegroundColor Yellow   
    $filecontent | ConvertTo-Json -Depth 3 | Format-Json -Indentation 2 | out-file $filename

    # pte-prod-release.yml
    Write-Host "Updating Release Pipeline" -ForegroundColor Green
    $fileName = ".azureDevOps\pte-prod-release.yml";
    $filecontent = Get-Content -Path $fileName;
    $content = '';

    foreach ($line in $fileContent) {
      $content = $content + "`n" + $line;
    }

    $yaml = ConvertFrom-YAML $content -Ordered;
    $yaml.resources.pipelines[0].source = "$repositoryName - CI";
    Write-Host " CI Pipeline `t $repositoryName - CI" -ForegroundColor Yellow

    $yaml | ConvertTo-Yaml | out-file $filename;
    #endregion

    #Update DevOps Repo with new setup
    & git add -A
    & git checkout -b main -q
    & git commit -m 'Initial commit' -q

    if (-not $localOnly) {
      Write-Host "Uploading files to repository branch main" -ForegroundColor Green      
      & git push -u origin main -q
    }
    
    Set-Location $oldLocation

    if (-not $localOnly) {
      $newRepoUrl = [uri]::EscapeUriString("https://$organisation@dev.azure.com/$organisation/$projectName/_git/$repositoryName")
      Write-Host "Repsitory created successfully, please clone from $newRepoUrl"
    }
    else {
      if (Test-Path -Path $localOnlyProjectPath) {
        Remove-Item -Path $localOnlyProjectPath -Recurse -Force
      }

      New-Item -Path $localOnlyProjectPath -ItemType Directory | Out-Null
      Copy-Item -Path "$tempProjectPath\*" -Destination $localOnlyProjectPath -Recurse -ErrorAction Stop

      Write-Host "Repository created locally at $localOnlyProjectPath" -ForegroundColor Green
      
      if ($openInVSCode) {
        & code $localOnlyProjectPath
      }
    }

    Set-Location $oldLocation

    if (Test-Path -Path $tempProjectPath) {
      Remove-Item -Path $tempProjectPath -Recurse -Force
    }

    if (Test-Path -Path $templatePath) {
      Remove-Item -Path $templatePath -Recurse -Force
    }
}

Export-ModuleMember -Function Initialize-BCSDevOpsRepository