# Automation Workflows

The below PowerShell script automates the process of importing layers into the Civillo. It handles authentication, interacts with the Civillo API to select organizations and projects, reads layer data from a CSV file, creates layers in the specified project, and assigns them to directories. The script provides user-friendly menus for organization and project selection and handles various aspects of the import process.

The below script requires PowerShell version 7.0 or above. To install PowerShell 7 please watch this installation video. (opens new window)

Users can copy, edit and save this script. To review how to use the script, you can run:

PS > get-help .\{name-of-script}.ps1

or

PS > get-help .\{name-of-script}.ps1 -Full

And can be run using the following parameters (at a minimum)

PS > .\{name-of-script}.ps1 \
        -civilloKey "{YOUR-CIVILLO-KEY}" \
        -civilloSecret "{YOUR-CIVILLO-SECRET}" \
        -inputcsv "PATH\TO\file.csv"

or

PS > .\{name-of-script}.ps1 \
        -civilloKey "{YOUR-CIVILLO-KEY}" \
        -civilloSecret "{YOUR-CIVILLO-SECRET}" \
        -inputCsv "PATH\TO\file.csv" \
        -organization "{organization nickname}"
        -projectId "{Project ID}"

An example CSV file can be downloaded from here.

# PowerShell script


<#
  .SYNOPSIS
  Imports local CAD, IFC and 12d files to a Civillo project, and assigns the layer to a directory.

  .DESCRIPTION
  Imports local CAD, IFC and 12d files to a Civillo project, and assigns the layer to a directory.

  .PARAMETER civilloKey
  Civillo Key. Log into Civillo and navigate to your profile (https://app.civillo.com/i/user/profile).
  At the bottom of the profile page, you can generate a new key or copy an existing key.
   
  .PARAMETER civilloSecret
  Civillo Secret. Log into Civillo and navigate to your profile (https://app.civillo.com/i/user/profile).
  At the bottom of the profile page, you can generate a new key with secret, or use your previously saved secret.
   
  .PARAMETER inputCsv
  Path to input CSV file which contains the following columns:
  File,LayerName,Description,Directory,srid,units,makeGlobal,convert3dFaces,convert3dFacesToTerrain
   
  .PARAMETER organization 
  Civillo organization nickname.
  This can be found in the URL of your civillo project. 
  E.g: https://app.civillo.com/{ORGANIZATION-NAME}/project/{PROJECT-ID}/?L9;V2#17/-37.75085/144.997355

  .PARAMETER projectId 
  Civillo project ID of the organization.
  This can be found in the URL of your civillo project. 
  E.g: https://app.civillo.com/{ORGANIZATION-NAME}/project/{PROJECT-ID}/?L9;V2#17/-37.75085/144.997355
  
  .EXAMPLE
  PS> .\civillo-import-csv.ps1 -civilloKey "{YOUR-CIVILLO-KEY}" -civilloSecret "{YOUR-CIVILLO-SECRET}" -inputCsv "{PATH/TO/CSV/FILE}"

  .EXAMPLE
  PS> .\civillo-import-csv.ps1 -civilloKey "{YOUR-CIVILLO-KEY}" -civilloSecret "{YOUR-CIVILLO-SECRET}" -inputCsv "{PATH/TO/CSV/FILE}" -organization "{ORGANIZATION ABBREVIATION}"

  .EXAMPLE
  PS> .\civillo-import-csv.ps1 -civilloKey "{YOUR-CIVILLO-KEY}" -civilloSecret "{YOUR-CIVILLO-SECRET}" -inputCsv "{PATH/TO/CSV/FILE}" -organization "{ORGANIZATION ABBREVIATION}" -projectId {PROJECT-ID}
#>

#Requires -Version 7.0
param (
    [Parameter(Position=1, Mandatory=$true)]
    [string]$civilloKey,
    [Parameter(Position=2, Mandatory=$true)]
    [string]$civilloSecret,
    [Parameter(Position=3, Mandatory=$true)]
    [string]$inputCsv,
    [Parameter(Position=4, Mandatory=$false)]
    [string]$organization,
    [Parameter(Position=5, Mandatory=$false)]
    [Int16]$projectId
)

Add-Type -AssemblyName System.Windows.Forms
# Functions
Function DrawMenu {
    ## Support function to the Menu function below
    param ($menuItems, $menuPosition, $menuTitel)
    $fcolor = $host.UI.RawUI.ForegroundColor
    $bcolor = $host.UI.RawUI.BackgroundColor
    $l = $menuItems.length + 1
    cls
    Write-Host "$menuTitel" -fore $fcolor -back $bcolor
    Write-Host ""
    Write-debug "L: $l MenuItems: $menuItems MenuPosition: $menuposition"
    for ($i = 0; $i -le $l;$i++) {
        Write-Host "  " -NoNewLine
        if ($i -eq $menuPosition) {
            Write-Host "$($menuItems[$i])" -fore $bcolor -back $fcolor
        } else {
            Write-Host "$($menuItems[$i])" -fore $fcolor -back $bcolor
        }
    }
}

Function Menu {
    ## Generate a small "DOS-like" menu.
    ## Choose a menuitem using up and down arrows, select by pressing ENTER
    param ([array]$menuItems, $menuTitel = "MENU")
    $vkeycode = 0
    $pos = 0
    DrawMenu $menuItems $pos $menuTitel
    While ($vkeycode -ne 13) {
        $press = $host.ui.rawui.readkey("NoEcho,IncludeKeyDown")
        $vkeycode = $press.virtualkeycode
        Write-host "$($press.character)" -NoNewLine
        If ($vkeycode -eq 38) {$pos--}
        If ($vkeycode -eq 40) {$pos++}
        if ($pos -lt 0) {$pos = $menuItems.length -1}
        if ($pos -ge $menuItems.length) {$pos = 0}
        DrawMenu $menuItems $pos $menuTitel
    }
    Write-Output $($menuItems[$pos])
}

Function Invoke-OpenFileDialog {
    ## Open a file dialog window to select CSV.
    Param (
        [Parameter(Mandatory=$true, Position=0)] [string] $DialogTitle,
        [Parameter(Mandatory=$true, Position=1)] [string] $InitialDirectory,
        [Parameter(Mandatory=$true, Position=2)] [string] $Filter,
        [Parameter(Mandatory=$true, Position=3)] [boolean] $TopMost
        )

    if ($InitialDirectory -ne "") {
        if (!(Test-Path -LiteralPath $InitialDirectory -PathType Container)) {
        $InitialDirectory = ([System.IO.FileInfo]$InitialDirectory).DirectoryName
        }
    }

    # Create a streaming image by streaming the base64 string to a bitmap stream source.
    $IconBase64 = "iVBORw0KGgoAAAANSUhEUgAAABsAAAAgCAYAAADjaQM7AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsEAAA7BAbiRa+0AAATQSURBVEhL7ZZ/TBxFFMd3Znfv9g6O3xypCVRSaFoCtS2QFkmhVFEoqRCLFStJTUHapPWPGk3xL1NtY2JMNRoTU4SmSI8fVUtTQgsmQikmgFZNCRxi0wrFMx7g3XFHub3d2fXNsSAgAv2hiUk/yWR33pu335k3b2eXecj/DqRd75uG+nqeZdlVDodDCA0LW8ViPB4TE3Nzc3LyHW3IvYmdrakJlmU5HloiQigJTFEqw8gcyw7JhFwLCQ4e4jhuOL+gwDEdMc2yYjabjW1va1sjimKqJElbMcaZhJBIcE3CSqwsx12FVbSDv/flsrKp6ajFWVSspaXF9JvNtsnn8+XAzAvg4eupHR5+Ay5f6vT6Vr1e31NUVOSm9pUyK/a73Y4vNTdvUQjZB+nJAYHVqqoyIObgeb6O1+lO5+Tmfh9lNhMt5K7xi1VVVpaBwCEQ2EAF/A6EZI7nTxoMhveKi4tH/cb7BFVUVJTLkvTOjMgMsJpXYQ/e17oPBAxpy1MVRev+haIoa4eGh7HWfSAgS031Lo9n8oIkE8jc/HqByvsRCuG8TqfrhHb9uT17xjTXPYFeueqOXne7I1s/8ctRH2HWavZ50ElAs0MbRBj3YYR69YIwBns8ABXqDQ8P/+Pbnh7H5pQUvDM3VwGbrIXOAx1rsgo/M0GHzWTS9pitSZSwoVwiasrCPVwInQCMIbB6Fe6psBNEMGRA9Hq9fdRHh4GN7j+PWfZd1H3ZYhqSzQe+kOIzbD5mKDmAfJw+2hQxIeJ9korzQTNyOeG50LF0InOhfUEQtvmtljNVu0WVTaoSMsy8quwnCFdGmgIsu8XuYTwxkuER1V0SYbaB5CN3IzwDHF234uLjN8xOoerTigpVkZ2XQp8eGZPUkxyjYgWhaxLmPl8dJFzJFPt/DXUPJnimyHbY20yiMokgHKiFLwkU2ZGS0tIPZsVq6xt0HpezGxFx8IeIHR/1ysa3WIVkUR9H04KYnzDHfS2y/MUEvdyX4WrDZNKRICp8FmHgvFSYdSBuWrhy2MOOLWlpTyQlJsrzkvtZdXW0x+Pp5JDi0gebSyzC4+mKKB7zKWoQ9dPBtEE1TrAY9TIs167yfHuS3jeY5v5GUe844r0EbxUJTvNJcrjRaLwRaDK9XlhYaJ+Jn8e5hoZkp9PZI8uSGGJkDzdGFba43O7j8B6+BKnzQ4Po7bSw3+KEKuiHT8x3E4i9EhMc0FX/VKiNeubyNzFKfV1dtsvlaoLzUqdjkSU6gi8vwQVBm2T3h15Z2SGB0qKBABU3C+ze2mdjazXTLIseR88XFX0VEhLyDLweXh9R994ala+fGD+X/UbMzTxDYMBOA4u76UMX1iXtwwP7eQ6dn7bM558m6AdWmDo+Pk5nuIb2MUbWQF45HrMxvfHE7agszud9UyRKKk0vFTKyiIkUcN6ZgthmOn4hS4pRKk6dioNDuRVOiFjapy8onAp9gZz8duTGjIufjIQ9OTE59SKImaIDuJrT+Y9a/IGLsKwY5UJjY5zdbrfAb0GqZvKLQlVag/TKkRf2H2rRzEuyok8I/LjcgH+N7fDOtGom/7FEFGW9lzEImmlZVrSyGawDA1xnR8drcFsKbRLSe/TAwYOX/c5/i66uLsOA1arTug/5L2GYPwFXOg3LzbJqMQAAAABJRU5ErkJggg=="
    $iconBytes  = [Convert]::FromBase64String($IconBase64)
    $stream     = New-Object IO.MemoryStream($iconBytes, 0, $iconBytes.Length)
    $stream.Write($iconBytes, 0, $iconBytes.Length)

    # Create a new form to hold the object and set it properties for the main
    # OpenFileDialog form.
    $OpenFileDialogForm         = New-Object System.Windows.Forms.Form
    $OpenFileDialogForm.Icon    = [System.Drawing.Icon]::FromHandle((New-Object System.Drawing.Bitmap -Argument $stream).GetHIcon())
    $OpenFileDialogForm.TopMost = $TopMost

    # Set the properties of the main OpenFileDialog along with using the
    # OpenFileDialogForm properties from above.
    $OpenFileDialog                  = New-Object System.Windows.Forms.OpenFileDialog
    $OpenFileDialog.Filter           = $Filter
    $OpenFileDialog.InitialDirectory = $InitialDirectory
    $OpenFileDialog.Title            = $DialogTitle
    $userClicked                     = $OpenFileDialog.ShowDialog($OpenFileDialogForm)
    $OpenFileDialog.Dispose()

    if ($userClicked -eq 'OK') { return $OpenFileDialog.FileName } else { return "" }
}

$civilloUriBase = "https://app.civillo.com/api/v1"

## Check for the Civillo Key parameter for this script;
if ($null -eq $civilloKey) {
    $civilloKey = read-host -Prompt "Enter Civillo Key: " 
}
## Check for the Civillo Secret parameter for this script;
if ($null -eq $civilloSecret) {
    $civilloSecret = read-host -Prompt "Enter Civillo Secret: " 
}
## Encode the Civillo Key and Secret into a Base64 string
$encoded = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes("$($civilloKey):$($civilloSecret)")) 

## Set the header authorization for the API requests
$headers = @{
    'Authorization' = "Bearer $($encoded)"
}

## Validate the CSV input
if ($null -eq $inputCsv) {
    $dialogTitle = 'Select a CSV file'
    $initialDirectory = (New-Object -ComObject Shell.Application).NameSpace('shell:Downloads').Self.Path
    $dialogFilter = 'CSV (*.csv)|*.csv'
    $inputCsv = Invoke-OpenFileDialog -DialogTitle $dialogTitle -InitialDirectory $initialDirectory -Filter $dialogFilter  -TopMost $true
}
if ($inputCsv -eq "")
{
    Write-Host "No CSV file supplied. Exiting." -ForegroundColor Yellow
    exit
}
$fileExists = Test-Path -Path $inputCsv -PathType Leaf
if ($fileExists -eq $false)
{
    Write-Host "No CSV file located at $($inputCsv). Exiting." -ForegroundColor Yellow
    exit
}

## Validate the input CSV has the correct headers
$csvHeaders = Get-Content -Path $inputCsv | Select-Object -First 1
$expectedHeaders = "file,layername,description,directory,srid,units,makeglobal,convert3dfaces,convert3dfacestoterrain"

if ($csvHeaders.ToLower() -ne $expectedHeaders) {
    Write-Host "Inconsistent CSV file headers. This script is expecting these column names in the input CSV: " -ForegroundColor Yellow
    Write-Host "---------------------------------------------------------------------------------------------------------"
    Write-Host "File, LayerName, Description, Directory, SRID, Units, MakeGlobal, Convert3dFaces, Convert3dFacesToTerrain"
    Write-Host "---------------------------------------------------------------------------------------------------------"
    Write-Host "Exiting." -ForegroundColor Yellow
    exit
}

$csv = Import-csv -path $inputCsv

## Processing Step 1. Select an Organization
if ([string]::IsNullOrEmpty($organization)) {

    $uri = "$($civilloUriBase)/applications"
    Try {
        $response = Invoke-RestMethod -Uri $uri -Headers $headers -Method Get -contenttype 'application/json'
    }
    Catch {
        Write-Host $_.ErrorDetails -ForegroundColor Red
        Write-Host $_.Exception.Message -ForegroundColor Red
        exit
    }

    If ($response.Count -eq 1) {
        $organization = $response[0].nickname
    } 
    ElseIf ($response.Count -lt 6) {
        $organizationArray = @()
        foreach($org in $response) {
            $organizationArray += $org.name
        }
        $selection = Menu $organizationArray "Please choose an organization:"
        foreach($org in $response) {
            if($org.name -eq $selection){
                $organization = $org.nickname
            }
        }
    }
    Else {
        $organizationFilter = Read-Host -Prompt "Set an organization filter"
        $organizationArray = @()
        foreach($org in $response) {
            if ($org.name -Match $organizationFilter) { 
                $organizationArray += $org.name
            }
        }
        $selection = Menu $organizationArray "Please choose an organization:"
        foreach($org in $response) {
            if($org.name -eq $selection){
                $organization = $org.nickname
            }
        }
    }
}

Start-Sleep -Seconds 1
## Processing Step 2. Select an organization's project
$uri = "$($civilloUriBase)/$($organization)/projects"
Try {
    $response = Invoke-RestMethod -Uri $uri -Headers $headers -Method Get -contenttype 'application/json'
}
Catch {
    Write-Host $_.ErrorDetails -ForegroundColor Red
    Write-Host $_.Exception.Message -ForegroundColor Red
    exit
}

$projectsArray = @()
foreach($app in $response) {
    $projectsArray += $app.name
}

if (-not($response.id -contains $projectId)) {
    $projectId = $null
}

if ([string]::IsNullOrEmpty($projectId) -Or $projectId -eq 0) {
    $selection = Menu $projectsArray "Please choose a project:"
    $projectId = 1;
    Write-Host "Using $selection project"
    foreach($app in $response) {
        if($app.name -eq $selection){
            $projectId = $app.id
        }
    }
}

## Processing Step 3. Select a list of valid SRS inputs for Civillo
Start-Sleep -Seconds 1
$uri = "$($civilloUriBase)/global/reference_systems"
Try {
    $projections = Invoke-RestMethod -Uri $uri -Headers $headers -Method Get -contenttype 'application/json'
}
Catch {
    Write-Host $_.ErrorDetails -ForegroundColor Red
    Write-Host $_.Exception.Message -ForegroundColor Red
}

## Processing Step 4. Fetch the current list of layers in the project
Start-Sleep -Seconds 1
$uri = "$($civilloUriBase)/$($organization)/projects/$($projectId)/layers"
Try {
    $projectLayers = Invoke-RestMethod -Uri $uri -Headers $headers -Method Get -contenttype 'application/json'
}
Catch {
    Write-Host $_.ErrorDetails -ForegroundColor Red
    Write-Host $_.Exception.Message -ForegroundColor Red
}


## Processing Step 5. Loop over the CSV rows.
foreach($line in $csv)
{     
    ## Processing Step 5.1. Validate the input file
    $outputFile = Split-Path $line.file -leaf
    $fileExists = Test-Path -Path $line.file -PathType Leaf
    if ($fileExists -eq $false)
    {
        Write-Host "$($outputFile) not located at $($line.file). Skipping." -ForegroundColor Yellow
        continue
    }

    ## Processing Step 5.2. This script will only process CAD. IFC and 12d files
    $knownExtensionsForScript = @(".dgn", ".dxf", ".dwg", ".ifc", ".12da", ".12daz")
    $fileExtension = [System.IO.Path]::GetExtension($line.file.ToLower())
    if ($knownExtensionsForScript.IndexOf($fileExtension) -eq -1) { 
        Write-Host "$($outputFile) will not be processes by this script. Only dgn, dxf, dwg, ifc, 12da and 12daz are accepted. Skipping." -ForegroundColor Yellow
        continue
    }

    ## Processing Step 5.3. Validate the CSV SRID value
    [int]$srid = -1
    foreach($projection in $projections) {
        if ($projection.name -Contains $line.srid ) { 
            $srid = $projection.value
        }
    }
    if ($srid -eq -1) {
        $defaultFilter = "GDA2020"
        # Using the Civillo projections, select the actual SRS.
        Write-Host "$($line.srid) not found as a Civillo projection. Please select a projection" -ForegroundColor Yellow
        [string]$projectionFilter = Read-Host -Prompt "Set a SRS filter  [$($defaultFilter)]"
        $projectionFilter = ($defaultFilter,$projectionFilter)[[bool]$projectionFilter]

        $projectionSeparatedList = @()
        foreach($projection in $projections) {
            if ($projection.name -Match $projectionFilter) { 
                $projectionSeparatedList += $projection.name
            }
        }
        $sridName = Menu $projectionSeparatedList "Select SRS for file $($outputFile):"
        foreach($projection in $projections) {
            if ($projection.name -Contains $sridName ) { 
                $srid = $projection.value
            }
        }
    }
    
    ## Processing Step 5.4. Validate the Units
    $unitsEnum = @("m", "mm")
    $units = $line.units.ToLower()
    if ($unitsEnum.IndexOf($units) -eq -1) { 
        $units = Menu $unitsEnum "Please choose a valid unit for the file $($outputFile):"
    }

    $trueFalseEnum = @("true", "false")
    ## Processing Step 5.5. Validate makeGlobal
    $makeGlobal = $line.makeglobal.ToLower()
    if ($trueFalseEnum.IndexOf($makeGlobal) -eq -1) { 
        $makeGlobal = Menu $trueFalseEnum "Please choose a valid `"Make Global`" for the file $($outputFile):"
    }

    ## Processing Step 5.5. Validate convert3dFaces
    $convert3dFaces = $line.convert3dFaces.ToLower()
    if ($trueFalseEnum.IndexOf($convert3dFaces) -eq -1) { 
        $convert3dFaces = Menu $trueFalseEnum "Please choose a valid `"Convert 3D Faces`" for the file $($outputFile):"
    }

    ## Processing Step 5.5. Validate convert3dFacesToTerrain
    $convert3dFacesToTerrain = $line.convert3dFacesToTerrain.ToLower()
    if ($trueFalseEnum.IndexOf($convert3dFaces) -eq -1) { 
        $convert3dFacesToTerrain = Menu $trueFalseEnum "Please choose a valid `"Convert 3D Faces to Terrain`" for the file $($outputFile):"
    }

    Write-Host "Processing $($outputFile)"  -ForegroundColor green
    [int64]$lastModifiedDate = Get-Date -Date ((Get-Item $line.file).LastWriteTime) -UFormat %s 
    $lastModifiedDate = $lastModifiedDate * 1000
    
    [int]$mode = 0
    [int]$replaceLayerId = 0
    #Does the layer already exist by name?
    $fileExistsInProject = $false
    foreach($layer in $projectLayers) {
        if ($layer.name.Trim() -eq $line.layername.Trim()) { 
            $mode = 1
            $replaceLayerId = $layer.layerId
            Start-Sleep -Seconds 1
            # Check to see if last revision fileLastModified matches our $lastModifiedDate
            $uri = "$($civilloUriBase)/$($organization)/projects/$($projectId)/layers/$($layer.layerId)"
            Try{
                $layerMetadata = Invoke-RestMethod -Uri $uri -Headers $headers -Method Get -contenttype 'application/json'
            }
            Catch {
                Write-Host $_.ErrorDetails -ForegroundColor Red
                Write-Host $_.Exception.Message -ForegroundColor Red
            }
            if ($layerMetadata) {
                $lastRevisionModifiedDate = $layerMetadata.revisions[0].source.fileLastModified
                if ($lastRevisionModifiedDate -eq $lastModifiedDate) {
                    Write-Host "$($outputFile) version currently loaded to the `"$selection`" project. Skipping."  -ForegroundColor Yellow
                    $fileExistsInProject = $true
                }
            }
        }
    }
    if ($fileExistsInProject) {
        continue
    }

    ## Processing Step 5.6. Create the body JSON as part of the layer create POST
    $body = @{"mode"="$($mode)"
        "fileNames"=@("$($outputFile)")
        "fileLastModifieds"=@($($lastModifiedDate))
        "title"="$($line.layername)"
        "permitSelf"="true"
        "makeGlobal"="$($makeGlobal)"
        "makeOnByDefault"="false"
        "srid"=$($srid)
        "description"="$($line.description)"
        "units"="$($units)"
        "convert3dFaces"="$($convert3dFaces)"
        "convert3dFacesToTerrain"="$($convert3dFacesToTerrain)"
    } | ConvertTo-Json

    if ($mode -eq 1) {
        $body = @{"mode"="$($mode)"
            "fileNames"=@("$($outputFile)")
            "replaceLayerId"=$replaceLayerId
            "fileLastModifieds"=@($($lastModifiedDate))
            "title"="$($line.layername)"
            "permitSelf"="true"
            "makeGlobal"="$($makeGlobal)"
            "makeOnByDefault"="false"
            "srid"=$($srid)
            "description"="$($line.description)"
            "units"="$($units)"
            "convert3dFaces"="$($convert3dFaces)"
            "convert3dFacesToTerrain"="$($convert3dFacesToTerrain)"
        } | ConvertTo-Json
    }

    ## Processing Step 5.7. Make POST preflight request to generate Civillo Job
    Start-Sleep -Seconds 1
    $uri = "$($civilloUriBase)/$($organization)/projects/$($projectId)/layers"
    Try {
        $preflight = Invoke-RestMethod -Uri $uri -Headers $headers -Method Post -Body $body -ContentType "application/json"
    }
    Catch {
        Write-Host $_.ErrorDetails -ForegroundColor Red
        Write-Host $_.Exception.Message -ForegroundColor Red
    }
    Start-Sleep -Seconds 1

    ## Processing Step 5.8. Upload the file to Civillo processPath
    # Construct the URL with query parameters
    $queryParams = @{
        'token' = $preflight.token
        'application' = $preflight.application
        'jobID' = $preflight.job
    }
    $queryString = ($queryParams.GetEnumerator() | ForEach-Object { $_.Key + '=' + [System.Uri]::EscapeDataString($_.Value) }) -join '&'
    $requestUri = $preflight.processPath + '?' + $queryString
    
    Try {
        $Form = @{file=Get-Item -Path $line.File}            
        $response = Invoke-RestMethod $requestUri -Method 'POST' -Form $Form
    }
    Catch {
        Write-Host $_.ErrorDetails -ForegroundColor Red
        Write-Host $_.Exception.Message
    }
    
    ## Processing Step 5.9. Request the progress of the Job every 30 seconds
    Start-Sleep -Seconds 1
    $completeResponse = $false
    $errorResponse = ""
    $counter = 1
    do
    {
        $counter++
        $uri = "$($civilloUriBase)/$($organization)/projects/$($projectId)/layers/processing?jobId=$($preflight.job)"
        $processing = Invoke-RestMethod -Uri $uri -Headers $headers -Method Get  -ContentType "application/json"
        $completeResponse = $processing.complete
        $errorResponse = $processing.error
        if ($errorResponse) {
            Write-Host "Error: " $description -ForegroundColor Red
            break
        }
        Write-Progress -Activity ">" -Status "$($processing.status):" -PercentComplete $processing.progress
        Start-Sleep -Seconds 30
        if ($counter -gt 50) {
            Write-Host "Processing $($outputFile) longer than 25 minutes. Aboring watch" -ForegroundColor Red
        }
    } while ($completeResponse -eq $false)
    Write-Progress -Completed -Activity "Completed"
}
# Refresh the list of layers
Start-Sleep -Seconds 1
$uri = "$($civilloUriBase)/$($organization)/projects/$($projectId)/layers"
Try {
    $projectLayers = Invoke-RestMethod -Uri $uri -Headers $headers -Method Get -contenttype 'application/json'
}
Catch {
    Write-Host $_.ErrorDetails -ForegroundColor Red
    Write-Host $_.Exception.Message -ForegroundColor Red
    exit
}

## Processing Step 6. Assign Layers to their directories
foreach($line in $csv)
{ 
    $outputFile = Split-Path $line.file -leaf
    foreach($layer in $projectLayers) {
        if ($layer.name -eq $line.layername) {
            Start-Sleep -Seconds 1
            $uri = "$($civilloUriBase)/$($organization)/projects/$($projectId)/layer-directory"
            Try {
                $body = @{"layerId"=$layer.layerId
                    "path"="$($line.directory)"
                } | ConvertTo-Json
                Invoke-RestMethod -Uri $uri -Headers $headers -Method Put -Body $body -contenttype 'application/json'
                Write-Host "$($outputFile) added to directory `"$($line.directory)`"." -ForegroundColor Green
            }
            Catch {
                Write-Host $_.ErrorDetails -ForegroundColor Red
                Write-Host $_.Exception.Message -ForegroundColor Red
                exit
            }
        }
    }
}

Write-Host "Finished layer import." -ForegroundColor Green
exit

Last Updated: 08/09/2023