Public/Api/Core/Projects/New-AzDoProject.ps1

function New-AzDoProject {
  <#
    .SYNOPSIS
        Function to create an Azure DevOps project
    .DESCRIPTION
        Function to create an Azure DevOps project
    .EXAMPLE
        New-AzDoProject -CollectionUri "https://dev.azure.com/contoso" -PAT "***" -ProjectName "Project 1"
 
        This example creates a new private Azure DevOps project
    .EXAMPLE
        New-AzDoProject -CollectionUri "https://dev.azure.com/contoso" -PAT "***" -ProjectName "Project 1" -Visibility 'public'
 
        This example creates a new public Azure DevOps project
    .EXAMPLE
        @("MyProject1","Myproject2") | New-AzDoProject -CollectionUri "https://dev.azure.com/contoso" -PAT "***"
 
        This example creates two new Azure DevOps projects using the pipeline.
 
    .EXAMPLE
        [pscustomobject]@{
            ProjectName = 'Project 1'
            Visibility = 'public'
            Description = 'This is the best project'
        },
        [pscustomobject]@{
            ProjectName = 'Project 1'
            Description = 'This is the best project'
        } | New-AzDoProject -PAT $PAT -CollectionUri $CollectionUri
 
        This example creates two new Azure DevOps projects using the pipeline.
    #>

  [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')]
  param (
    # Collection URI. e.g. https://dev.azure.com/contoso.
    # Azure Pipelines has a predefined variable for this.
    [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
    [ValidateScript({ Validate-CollectionUri -CollectionUri $_ })]
    [string]
    $CollectionUri,

    # Name of the project.
    [Parameter(Mandatory, ValueFromPipelineByPropertyName, ValueFromPipeline)]
    [string[]]
    [ValidateScript(
      {
        # Length
        if (($_).Length -gt 63) {
          throw "The project name cannot be longer than 63 characters"
        }

        # - Must not be a system reserved name.
        $forbiddenNames = @(
          'COM1',
          'COM2',
          'COM3',
          'COM4',
          'COM5',
          'COM6',
          'COM7',
          'COM8',
          'COM9',
          'COM10',
          'CON',
          'DefaultCollection',
          'LPT1',
          'LPT2',
          'LPT3',
          'LPT4',
          'LPT5',
          'LPT6',
          'LPT7',
          'LPT8',
          'LPT9',
          'NUL',
          'PRN',
          'SERVER',
          'SignalR',
          'Web',
          'WEB')
        if ($forbiddenNames -contains $_) {
          throw "The project name cannot be one of the following values: $forbiddenNames"
        }

        # - Must not be one of the hidden segments used for IIS request filtering like App_Browsers, App_code, App_Data, App_GlobalResources, App_LocalResources, App_Themes, App_WebResources, bin, or web.config.
        if ($_ -match "App_Browsers|App_code|App_Data|App_GlobalResources|App_LocalResources|App_Themes|App_WebResources|bin|web.config") {
          throw "The project name cannot be one of the following values: App_Browsers, App_code, App_Data, App_GlobalResources, App_LocalResources, App_Themes, App_WebResources, bin, or web.config"
        }

        # - Must not contain any Unicode control characters or surrogate characters.
        if ($_ -match "[\p{C}\p{Cs}]") {
          throw "The project name cannot contain any Unicode control characters or surrogate characters"
        }
        # - Must not contain the following printable characters: \ / : * ? " < > | ; # $ * { } , + = [ ].
        if ($_ -match "[\\/:*?`"<>|;#$*{},+=\[\]]") {
          throw "The project name cannot contain the following printable characters: \ / : * ? `"< > | ; # $ * { } , + = [ ]"
        }
        # - Must not start with an underscore _.
        if ($_ -match "^_") {
          throw "The project name cannot start with an underscore _"
        }
        # - Must not start or end with a period ..
        if ($_ -match "^\." -or $_ -match "\.$") {
          throw "The project name cannot start or end with a period ."
        }
        $true
      }
    )]
    $ProjectName,

    # Description of the project
    [Parameter()]
    [string]
    $Description = '',

    # Type of source control.
    [Parameter()]
    [string]
    $SourceControlType = 'GIT',

    # Visibility of the project (private or public).
    [Parameter()]
    [ValidateSet('private', 'public')]
    [string]
    $Visibility = 'private'
  )

  begin {
    Write-Verbose "Starting function: New-AzDoProject"
  }

  process {

    $params = @{
      uri     = "$CollectionUri/_apis/projects"
      version = "7.2-preview.4"
      method  = 'POST'
    }

    foreach ($name in $ProjectName) {

      $body = @{
        name         = $name
        description  = $Description
        visibility   = $Visibility
        capabilities = @{
          versioncontrol  = @{
            sourceControlType = $SourceControlType
          }
          processTemplate = @{
            templateTypeId = '6b724908-ef14-45cf-84f8-768b5384da45'
          }
        }
      }

      if ($PSCmdlet.ShouldProcess($CollectionUri, "Create project named: $($PSStyle.Bold)$name$($PSStyle.Reset)")) {
        try {
          $body | Invoke-AzDoRestMethod @params | Out-Null
        } catch {
          if ($_ -match 'TF200019') {
            Write-Warning "Project $name already exists, trying to get it"
          } else {
            Write-AzDoError -message $_
          }
        }

        do {
          Start-Sleep 5
          Write-Verbose "Fetching creation state of $name"
          $getAzDoProjectSplat = @{
            CollectionUri = $CollectionUri
            ProjectName   = $name
          }

          $response = Get-AzDoProject @getAzDoProjectSplat
        } while (
          $response.State -ne 'wellFormed'
        )
        $result += ($response)
      } else {
        Write-Verbose "Calling Invoke-AzDoRestMethod with $($params| ConvertTo-Json -Depth 10)"
      }
    }
  }

  end {
    if ($result) {
      $result
    }
  }
}