BCDeploy.psm1



function GetToken {
    param (
        [string]$tenantId = "",
        [string]$clientId = "",
        [string]$clientSecret = ""
    )
    $body = @{grant_type="client_credentials"; scope="https://api.businesscentral.dynamics.com/.default"; client_id=$clientID; client_secret=$clientSecret; client_info=1}
    $token = (Invoke-RestMethod -Method Post -Uri $("https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token") -Body $body).access_token
    return $token;
}

function FindApp
{
    param(
        [string]$folder,
        [string]$app
    )
    $file = (Get-ChildItem -Path $folder -Filter "*_${app}_*.app")
    $filepath = $file.FullName;
    $info = $file.Name.TrimEnd(".app").Split('_');
    return @($info[0], $info[1], $info[2], $filepath);
}

function GetCompanyId {
    param (
        [string]$token = "",
        [string]$instance = ""
    )

    $baseuri = "https://api.businesscentral.dynamics.com/v2.0/${instance}/api/microsoft/automation/v2.0";
    $companies = Invoke-RestMethod -Method Get -Uri "${baseuri}/companies" -Headers @{Authorization = "Bearer ${token}" }
    $companyId = $Companies.value[0].id
    return $companyId
}

function GetStatus {
    param (
        [string]$token = "",
        [string]$instance = "",
        [string]$companyId = "",
        [string]$publisher = "",
        [string]$name = "",
        [string]$version = ""
    )
    try{
        $baseuri = "https://api.businesscentral.dynamics.com/v2.0/${instance}/api/microsoft/automation/v2.0";
        $response = Invoke-RestMethod -Method Get `
            -Uri "${baseuri}/companies($companyId)/extensionDeploymentStatus" `
            -Headers @{Authorization = "Bearer ${token}" }
        Write-Debug  ($response.value |Out-String)
        $match = $response.value `
            | Where-Object { $_.publisher -eq $publisher -and $_.name -eq $name -and $_.appVersion -eq $version }
        Write-Debug  ($match |Out-String)
        if(@($match).count -gt 0)
        {
            return $match[0].status;
        }
        else {
            return "Unknown";
        }
    }
    catch{
        return "Unknown";
    }
}

function GetUpload {
    param (
        [string]$token = "",
        [string]$instance = "",
        [string]$companyId = ""
    )
    $baseuri = "https://api.businesscentral.dynamics.com/v2.0/${instance}/api/microsoft/automation/v2.0";
    $result = Invoke-RestMethod -Method Get `
        -Uri "${baseuri}/companies($companyId)/extensionUpload" `
        -Headers @{Authorization = "Bearer ${token}" }

    return $result.value[0]
}

function PostUpload {
    param (
        [string]$token = "",
        [string]$instance = "",
        [string]$companyId = ""
    )
    $baseuri = "https://api.businesscentral.dynamics.com/v2.0/${instance}/api/microsoft/automation/v2.0";
    $result = Invoke-RestMethod -Method Post `
        -Uri "${baseuri}/companies($companyId)/extensionUpload" `
        -Headers @{Authorization = "Bearer ${token}" } `
        -ContentType "application/json" `
        -Body ( @{
            "@odata.type"="#Microsoft.NAV.extensionUpload";
            "systemId"=[guid]::NewGuid();
            "schedule"="Current version";
        } | ConvertTo-Json )

    return $result.value
}

function PutUpload {
    param (
        [string]$token = "",
        [string]$instance = "",
        [string]$companyId = "",
        [string]$uploadId = "",
        [string]$path = ""
    )
    $baseuri = "https://api.businesscentral.dynamics.com/v2.0/${instance}/api/microsoft/automation/v2.0"
    Invoke-RestMethod `
    -Method Put `
    -Uri $("${baseuri}/companies(${companyId})/extensionUpload($uploadId)/extensionContent") `
    -Headers @{Authorization='Bearer ' + $token;'If-Match'='*'} `
    -ContentType "application/octet-stream" `
    -InFile $path
}

function UploadUpload {
    param (
        [string]$token = "",
        [string]$instance = "",
        [string]$companyId = "",
        [string]$uploadId = ""
    )
    $baseuri = "https://api.businesscentral.dynamics.com/v2.0/${instance}/api/microsoft/automation/v2.0";
    Invoke-RestMethod -Method Post `
        -Uri "${baseuri}/companies(${companyId})/extensionUpload($uploadId)/Microsoft.NAV.upload" `
        -Headers @{Authorization = "Bearer ${token}" }
}

function GetExtension {
    param (
        [string]$token = "",
        [string]$instance = "",
        [string]$companyId = "",
        [string]$name = "",
        [string]$version = ""
    )
    $baseuri = "https://api.businesscentral.dynamics.com/v2.0/${instance}/api/microsoft/automation/v2.0";
    $result = Invoke-RestMethod -Method Get `
        -Uri "${baseuri}/companies($companyId)/extensions" `
        -Headers @{Authorization = "Bearer ${token}" }

    $match = $result.value `
        | Where-Object { $_.displayName -eq $name -and "$($_.versionMajor).$($_.versionMinor).$($_.versionBuild).$($_.versionRevision)" -eq $version }

    return $match[0];
}

function InstallExtension {
    param (
        [string]$token = "",
        [string]$instance = "",
        [string]$companyId = "",
        [string]$packageId = ""
    )
    $baseuri = "https://api.businesscentral.dynamics.com/v2.0/${instance}/api/microsoft/automation/v2.0";
    Invoke-RestMethod -Method Post `
        -Uri "${baseuri}/companies(${companyId})/extension($packageId)/Microsoft.NAV.install" `
        -Headers @{Authorization = "Bearer ${token}" }
}

function Publish-Extension {
    param (
        [string]$tenantId = "",
        [string]$clientId = "",
        [string]$clientSecret = "",
        [string]$instance = "",
        [string]$folder = "",
        [string]$app = ""
    )
    $appinfo = FindApp -folder $folder -app $app
    $publisher  = $appinfo[0];
    $name       = $appinfo[1];
    $version    = $appinfo[2];
    $path       = $appinfo[3];
    "Publishing $publisher - $name - $version ($path)"

    $token = GetToken -tenantId $tenantId -clientId $clientId -clientSecret $clientSecret
    $companyId = GetCompanyId -token $token -instance $instance
    $upload = GetUpload -token $token -instance $instance -companyId $companyId
    if($upload)
    {
        "Using existing upload ($($upload.systemId))"
    }
    else
    {
        PostUpload -token $token -instance $instance -companyId $companyId
        $upload = GetUpload -token $token -instance $instance -companyId $companyId
        "Created new upload ($($upload.systemId))"
    }

    PutUpload -token $token -instance $instance -companyId $companyId -uploadId $upload.systemId -path $path
    "Uploaded app contents to $($upload.systemId)"
    UploadUpload  -token $token -instance $instance -companyId $companyId -uploadId $upload.systemId
    "Uploaded app $($upload.systemId)"
    do
    {
        Start-Sleep 5
        $status = GetStatus -token $token -instance $instance -companyId $companyId -publisher $publisher -name $name -version $version
        "$tenantId $instance $publisher $name $version - $status"
        if($status -eq "Failed"){ throw "The extension upload failed." }
        #if($status -eq "Unknown"){ throw "The extension upload status in unknown" }
    }
    while($status -ne "Completed")
    
    $extension = GetExtension -token $token -instance $instance -companyId $companyId -name $name -version $version
    if($extension.isInstalled -eq $false)
    {
        InstallExtension  -token $token -instance $instance -companyId $companyId -packageId $extension.packageId
        "Installed app $($extension.packageId)"
    }
    else 
    {
        "App already installed $($extension.packageId)"
    }
}