Public/Update-ApplicationOwner.ps1

#Requires -Version 7

$ErrorActionPreference = "Stop"
$InformationPreference = "Continue"

function Update-ApplicationOwner {
    <#
        .SYNOPSIS
        Export DPC applications.
        .DESCRIPTION
        Export applications and users from DPC for use in Application Owner.
        .PARAMETER DpcInstance
        DPC instance. For example, https://myinstance.dashworks.app:8443
        .PARAMETER DpcApiKey
        DPC API Key.
        .PARAMETER AoInstance
        AOM instance. For example, https://myinstance.juriba.app
        .PARAMETER AoApiKey
        AOM API Key.
        .PARAMETER InputBatchLength
        The maximum number of records returned in a single call to DPC.
        .PARAMETER InputBatchStartOffset
        The offset within the initial batch to start from, can be used to skip previously processed data.
        .PARAMETER MaximumAppsToImport
        The maximum number of applications to process, or 0 for all.
        .PARAMETER OutputBatchLength
        The maximum number of records sent in a single call to AOM.
        .EXAMPLE
        PS> Update-ApplicationOwner https://myinstance.dashworks.app:8443 pCyty***** https://myinstance.juriba.app hoF7k*****
    #>

    [CmdletBinding(SupportsShouldProcess)]
    param(
        [Parameter(Mandatory=$true)]
        [string]$DpcInstance,
        [Parameter(Mandatory=$true)]
        [string]$DpcApiKey,
        [Parameter(Mandatory=$true)]
        [string]$AoApiKey,
        [Parameter(Mandatory=$false)]
        [string]$AoInstance = "https://ao.juriba.app",
        [Parameter(Mandatory=$false)]
        [ValidateRange(1, 10000)]
        [int]$InputBatchLength = 10000,
        [Parameter(Mandatory=$false)]
        [ValidateScript({
            if ($_ -ge 0) {
                $true
            } else {
                throw "Value must be greater than or equal to 0."
            }
        })]
        [int]$InputBatchStartOffset = 0,
        [Parameter(Mandatory=$false)]
        [ValidateScript({
            if ($_ -ge 0) {
                $true
            } else {
                throw "Value must be greater than or equal to 0."
            }
        })]
        [int]$MaximumAppsToImport = 0,
        [Parameter(Mandatory=$false)]
        [ValidateRange(1, 10000)]
        [int]$OutputBatchLength = 10000
    )

    $me = Invoke-RestMethod -Method Get -Uri "$AoInstance/api/me" -Headers @{'x-api-key' = $AoApiKey}
    if ($me.tenants.Count -ne 1) {
        throw "Expected 1 tenant, found $($me.tenants.Count)."
    }
    $tenantId = $me.tenants[0].id

    $subscription = Invoke-RestMethod -Method Get -Uri "$AoInstance/api/tenant/$tenantId/subscription" -Headers @{'x-api-key' = $AoApiKey}
    if ($null -eq $subscription.type) {
        throw "Unable to obtain subscription details for tenant $tenantId."
    }
    $userLimit = 0
    if ($subscription.type -eq "Trial") {
        $userLimit = $subscription.usersQuota.total ? $subscription.usersQuota.total : 100
        $inUse = $subscription.usersQuota.used ? $subscription.usersQuota.used : 0
        Write-Information "Trial is limited to $userLimit users, with $inUse already in use."
    }

    $tenancyDetails = Invoke-RestMethod -Method Get -Uri "$AoInstance/api/tenant/$tenantId/admin-settings" -Headers @{'x-api-key' = $AoApiKey}
    if ($null -eq $tenancyDetails.checkInIntervalInDays) {
        throw "Unable to obtain tenancy details for tenant $tenantId."
    }

    # Initialize an empty array to store the results
    $results = @()

    # Loop until no more data is returned from the REST call or the limit is reached
    do {
        $BatchLength = $InputBatchLength
        Write-Information "Fetching next $BatchLength records from DPC, starting from $InputBatchStartOffset"
        # Build the URI with the dynamic parameters
        $uri = "$DpcInstance/apiv1/applications?`$top=$BatchLength&`$skip=$InputBatchStartOffset&`$select=packageName,packageManufacturer,packageVersion,ownerEMailAddress,ownerDisplayName,ownerDistinguishedName,ownerCommonName"

        # Invoke the curl command to get the JSON response
        $jsonResponse = Invoke-RestMethod -Method Get -Uri $uri -Headers @{
            'X-Api-Key' = $DpcApiKey
        }
        # Append the results defined in the JSON to the existing results array
        $jsonResponse.results | ForEach-Object {
            # Perform the projection here
            # Example: Select specific properties from the JSON response
            if ($MaximumAppsToImport -ne 0 -and $results.Count -ge $MaximumAppsToImport) {
                break
            }
            elseif (![string]::IsNullOrEmpty($_.packageName)) {
                $results += [PSCustomObject]@{
                    packageName = $_.packageName
                    packageManufacturer = if ($_.packageManufacturer) { $_.packageManufacturer } else { "Unknown" }
                    packageVersion = if ($_.packageVersion) { $_.packageVersion } else { "Unknown" }
                    ownerEMailAddress = $_.ownerEMailAddress
                    ownerDisplayName = $_.ownerDisplayName
                    ownerDistinguishedName = $_.ownerDistinguishedName
                    ownerCommonName = $_.ownerCommonName
                }
            }
        }

        # Increment the InputBatchStartOffset by BatchLength for the next iteration
        $InputBatchStartOffset += $BatchLength
    } while ($jsonResponse.results -and ($MaximumAppsToImport -eq 0 -or $results.Count -lt $MaximumAppsToImport))

    if ($results.Count -eq 0) {
        Write-Information "No data found in DPC. Upload terminated."
    } else {
        # Group the results by packageName
        [array]$groupedResults = $results | Group-Object -Property packageName

        Write-Information "Fetched $($results.Count) results with $($groupedResults.Count) unique packages."

        # Access the required properties from the grouped results
        foreach ($group in $groupedResults) {
            $packageName = $group.Name
            $groupedApps = $group.Group
            [array]$uniqueManufacturers = $groupedApps | Select-Object -ExpandProperty packageManufacturer -Unique
            $manufacturerCount = $uniqueManufacturers.Count
            if ($manufacturerCount -gt 1) {
                Write-Debug "More than one manufacturer found for package $packageName."
            }
        }

        # create JSON for import to aom using /api/tenant/{tenantId}/application/bulk-import
        $aomData = @()
        $users = @{}
        foreach ($group in $groupedResults) {
            $packageName = $group.Name
            [array]$allVersions = $group.Group | Select-Object -ExpandProperty packageVersion -Unique | Where-Object { $_ -ne "Unknown" } | Sort-Object -Descending
            if ($null -eq $allVersions) {
                $highestVersion = "Unknown"
            } else {
                $highestVersion = $allVersions[0]
            }
            if ($users.ContainsKey($group.Group[0].ownerEMailAddress)) {
                $users.Add($group.Group[0].ownerEMailAddress, $group.Group[0].ownerDisplayName)
            }
            
            $aomData += [PSCustomObject]@{
                name = $group.Name
                manufacturer = $group.Group[0].packageManufacturer
                currentVersion = $highestVersion
                ownerEmail = $group.Group[0].ownerEMailAddress
                ownerName = $group.Group[0].ownerDisplayName
                ownerNotRequired = $false
                checkInInterval = "$($tenancyDetails.checkInIntervalInDays)"
            }
        }

        $OutputBatchOffset = 0
        $Failed = $false
        do {
            $headers = @{
                'x-api-key' = $AoApiKey
                'Content-Type' = 'application/json'
            }
            try {
                [array]$batch = $aomData | Select-Object -Skip $OutputBatchOffset -First $OutputBatchLength
                Write-Information "Uploading packages $($OutputBatchOffset + 1) to $($OutputBatchOffset + $batch.Count) of $($aomData.Count)"
                $response = Invoke-RestMethod -Method Post -Uri "$AoInstance/api/tenant/$tenantId/application/bulk-import" -Body (@{ "applications" = $batch } | ConvertTo-Json -Depth 5) -Headers $headers    
                Write-Information " Status: $($response.imported) packages, $($response.failed) failed."
                $OutputBatchOffset += $OutputBatchLength
            }
            catch {
                $response = $_ | ConvertFrom-Json -AsHashtable
                Write-Warning $response.title
                if ($null -ne $response['detail']) {
                    Write-Warning " $($response['detail'])."
                }
                if ($null -ne $response['errors']) {
                    Write-Warning "There were $($response.errors.Keys.Count) errors."
                    foreach ($Key in $response["errors"].Keys) {
                        foreach ($item in $response["errors"][$Key]) {
                            Write-Error -Message $item["message"] -ErrorAction Continue
                        }
                    }
                }
                $Failed = $true
            }
        } while (!$Failed -and $OutputBatchOffset -lt $aomData.Count);

        if (!$Failed) {
            Write-Information "Upload complete"
        } else {
            Write-Information "Upload FAILED"
        }
    }
}