functions/Get-BapEnvironmentD365App.ps1


<#
    .SYNOPSIS
        Get D365 App from the environment
         
    .DESCRIPTION
        Enables the user to analyze and validate the current D365 Apps and their state, on a given environment
         
        It can show all available D365 Apps - including their InstallState
         
        It can show only installed D365 Apps
         
        It can show only installed D365 Apps, with available updates
         
    .PARAMETER EnvironmentId
        The id of the environment that you want to work against
         
        This can be obtained from the Get-BapEnvironment cmdlet
         
    .PARAMETER Name
        Name of the D365 App / Package that you are looking for
         
        It supports wildcard searching, which is validated against the following properties:
        * AppName / ApplicationName
        * PackageName / UniqueName
         
    .PARAMETER InstallState
        Instruct the cmdlet which install states that you want to have included in the output
         
        The default value is: "All"
         
        Valid values:
        * "All"
        * "Installed"
        * "None"
         
    .PARAMETER GeoRegion
        Instructs the cmdlet which Geo / Region the environment is located
         
        The default value is: "Emea"
         
        This is mandatory field from the API specification, we don't have the full list of values at the time of writing
         
    .PARAMETER UpdatesOnly
        Instruct the cmdlet to only output D365 Apps that has an update available
         
        Makes it easier to fully automate the update process of a given environment
         
    .PARAMETER AsExcelOutput
        Instruct the cmdlet to output all details directly to an Excel file
         
        This makes it easier to deep dive into all the details returned from the API, and makes it possible for the user to persist the current state
         
    .EXAMPLE
        PS C:\> Get-BapEnvironmentD365App -EnvironmentId eec2c11a-a4c7-4e1d-b8ed-f62acc9c74c6
         
        This will query the environment for ALL available D365 Apps.
        It will compare available vs installed D365 Apps, and indicate whether an update is available of not.
         
        Sample output:
         
        PackageId PackageName AvailableVersion InstalledVersion UpdateAvailable
        --------- ----------- ---------------- ---------------- ---------------
        cea6753e-9c74-4aa9-85a1-5869105115d3 msdyn_ExportControlAnchor 1.0.2553.1 N/A
        ea8d3b2f-ede2-46b4-900d-ed02c81c44fd AgentProductivityToolsAnchor 9.2.24021.1005 9.2.24019.1005 True
        b1676368-b448-4fbd-a238-9b6ddc36be81 SharePointFormProcessing 202209.5.2901.0 N/A
        1c0a1237-9408-4b99-9fec-39696d99287b msdyn_AppProfileManagerAnchor 10.1.24021.1005 10.1.24021.1005 False
        9f4c778b-2f0b-416f-8166-e96da680ffb2 mpa_AwardsAndRecognition 1.0.0.32 N/A
        6ce2d70e-78bf-4ff6-85ed-1bd63d4ab444 ExportToDataLakeCoreAnchor 1.0.0.1 1.0.0.1 False
         
    .EXAMPLE
        PS C:\> Get-BapEnvironmentD365App -EnvironmentId eec2c11a-a4c7-4e1d-b8ed-f62acc9c74c6 -InstallState Installed
         
        This will query the environment for installed only D365 Apps.
        It will compare available vs installed D365 Apps, and indicate whether an update is available of not.
         
        Sample output:
        PackageId PackageName AvailableVersion InstalledVersion UpdateAvailable
        --------- ----------- ---------------- ---------------- ---------------
        ea8d3b2f-ede2-46b4-900d-ed02c81c44fd AgentProductivityToolsAnchor 9.2.24021.1005 9.2.24019.1005 True
        1c0a1237-9408-4b99-9fec-39696d99287b msdyn_AppProfileManagerAnchor 10.1.24021.1005 10.1.24021.1005 False
        6ce2d70e-78bf-4ff6-85ed-1bd63d4ab444 ExportToDataLakeCoreAnchor 1.0.0.1 1.0.0.1 False
         
    .EXAMPLE
        PS C:\> Get-BapEnvironmentD365App -EnvironmentId eec2c11a-a4c7-4e1d-b8ed-f62acc9c74c6 -InstallState None
         
        This will query the environment for NON-installed only D365 Apps.
        It will output all details available for the D365 Apps.
         
        Sample output:
        PackageId PackageName AvailableVersion InstalledVersion UpdateAvailable
        --------- ----------- ---------------- ---------------- ---------------
        cea6753e-9c74-4aa9-85a1-5869105115d3 msdyn_ExportControlAnchor 1.0.2553.1 N/A
        b1676368-b448-4fbd-a238-9b6ddc36be81 SharePointFormProcessing 202209.5.2901.0 N/A
        9f4c778b-2f0b-416f-8166-e96da680ffb2 mpa_AwardsAndRecognition 1.0.0.32 N/A
         
    .EXAMPLE
        PS C:\> Get-BapEnvironmentD365App -EnvironmentId eec2c11a-a4c7-4e1d-b8ed-f62acc9c74c6 -Name "*ProviderAnchor*"
         
        This will query the environment for ALL D365 Apps.
        It will filter the output to only those who match the search pattern "*ProviderAnchor*".
        It will compare available vs installed D365 Apps, and indicate whether an update is available of not.
         
        Sample output:
        PackageId PackageName AvailableVersion InstalledVersion UpdateAvailable
        --------- ----------- ---------------- ---------------- ---------------
        c0cb37fd-d7f4-40f2-8592-64ec71a2c508 msft_ConnectorProviderAnchor 9.0.0.1618 9.0.0.1618 False
         
    .EXAMPLE
        PS C:\> Get-BapEnvironmentD365App -EnvironmentId eec2c11a-a4c7-4e1d-b8ed-f62acc9c74c6 -UpdatesOnly
         
        This will query the environment for ALL available D365 Apps.
        It will compare available vs installed D365 Apps, and indicate whether an update is available of not.
        It will filter the output to only containing those who have an update available.
         
        Sample output:
        PackageId PackageName AvailableVersion InstalledVersion UpdateAvailable
        --------- ----------- ---------------- ---------------- ---------------
        ea8d3b2f-ede2-46b4-900d-ed02c81c44fd AgentProductivityToolsAnchor 9.2.24021.1005 9.2.24019.1005 True
         
    .EXAMPLE
        PS C:\> $appIds = @(Get-BapEnvironmentD365App -EnvironmentId eec2c11a-a4c7-4e1d-b8ed-f62acc9c74c6 -InstallState Installed -UpdatesOnly | Select-Object -ExpandProperty PackageId)
        PS C:\> Invoke-BapEnvironmentInstallD365App -EnvironmentId eec2c11a-a4c7-4e1d-b8ed-f62acc9c74c6 -PackageId $appIds
         
        This will query the environment for installed only D365 Apps.
        It will filter the output to only containing those who have an update available.
        It will persist the PackageIds for each D365 App, into an array.
        It will invoke the installation process using the Invoke-BapEnvironmentInstallD365App cmdlet.
         
    .EXAMPLE
        PS C:\> Get-BapEnvironmentD365App -EnvironmentId eec2c11a-a4c7-4e1d-b8ed-f62acc9c74c6 -AsExcelOutput
         
        This will query the environment for ALL available D365 Apps.
        It will compare available vs installed D365 Apps, and indicate whether an update is available of not.
        Will output all details into an Excel file, that will auto open on your machine.
         
    .NOTES
        Author: Mötz Jensen (@Splaxi)
#>

function Get-BapEnvironmentD365App {
    [CmdletBinding()]
    [OutputType('System.Object[]')]
    param (
        [parameter (mandatory = $true)]
        [string] $EnvironmentId,

        [string] $Name = "*",

        [ValidateSet("All", "Installed", "None")]
        [string] $InstallState = "All",

        [string] $GeoRegion = "Emea",

        [switch] $UpdatesOnly,

        [switch] $AsExcelOutput
    )
    
    begin {
        $tenantId = (Get-AzContext).Tenant.Id
        
        # Make sure all *BapEnvironment* cmdlets will validate that the environment exists prior running anything.
        $envObj = Get-BapEnvironment -EnvironmentId $EnvironmentId | Select-Object -First 1

        if ($null -eq $envObj) {
            $messageString = "The supplied EnvironmentId: <c='em'>$EnvironmentId</c> didn't return any matching environment details. Please verify that the EnvironmentId is correct - try running the <c='em'>Get-BapEnvironment</c> cmdlet."
            Write-PSFMessage -Level Host -Message $messageString
            Stop-PSFFunction -Message "Stopping because environment was NOT found based on the id." -Exception $([System.Exception]::new($($messageString -replace '<[^>]+>', '')))
        }
        
        if (Test-PSFFunctionInterrupt) { return }

        # First we will fetch ALL available apps for the environment
        $tokenPowerApi = Get-AzAccessToken -ResourceUrl "https://api.powerplatform.com/"
        $headersPowerApi = @{
            "Authorization" = "Bearer $($tokenPowerApi.Token)"
        }
        
        $appsAvailable = Invoke-RestMethod -Method Get -Uri "https://api.powerplatform.com/appmanagement/environments/$EnvironmentId/applicationPackages?api-version=2022-03-01-preview" -Headers $headersPowerApi | Select-Object -ExpandProperty Value

        # Next we will fetch current installed apps and their details, for the environment
        $uriSourceEncoded = [System.Web.HttpUtility]::UrlEncode($envObj.LinkedMetaPpacEnvUri)
        $tokenAdminApi = Get-AzAccessToken -ResourceUrl "065d9450-1e87-434e-ac2f-69af271549ed"
        $headersAdminApi = @{
            "Authorization" = "Bearer $($tokenAdminApi.Token)"
        }

        $appsEnvironment = Invoke-RestMethod -Method Get -Uri "https://api.admin.powerplatform.microsoft.com/api/AppManagement/InstancePackages/instanceId/$tenantId`?instanceUrl=$uriSourceEncoded`&geoType=$GeoRegion" -Headers $headersAdminApi
    }
    
    process {
        if (Test-PSFFunctionInterrupt) { return }

        $resCol = @(
            foreach ($appObj in $($appsAvailable | Sort-Object -Property ApplicationName)) {
                if ((-not ($appObj.ApplicationName -like $Name -or $appObj.ApplicationName -eq $Name)) -and (-not ($appObj.UniqueName -like $Name -or $appObj.UniqueName -eq $Name))) { continue }
                if ($InstallState -ne "All" -and $appObj.state -ne $InstallState) { continue }
            
                $appObj | Add-Member -MemberType NoteProperty -Name CurrentVersion -Value "N/A"

                $currentApp = $appsEnvironment | Where-Object ApplicationId -eq $appObj.ApplicationId | Select-Object -First 1
                if ($currentApp) {
                    $appObj.CurrentVersion = $currentApp.Version
                
                    $appObj | Add-Member -MemberType NoteProperty -Name IsLatest -Value $($appObj.CurrentVersion -eq $appObj.Version)
                    $appObj | Add-Member -MemberType NoteProperty -Name UpdateAvail -Value $(-not ($appObj.CurrentVersion -eq $appObj.Version))
                }
            
                $appObj | Select-PSFObject -TypeName "D365Bap.Tools.Package" -Property "Id as PackageId",
                "UniqueName as PackageName",
                "Version as AvailableVersion",
                "CurrentVersion as InstalledVersion",
                "UpdateAvail as UpdateAvailable",
                "ApplicationName as AppName",
                "state as InstallState",
                *,
                @{Name = "SupportedCountriesList"; Expression = { $_.supportedCountries -join "," } }
            }
        )

        if ($UpdatesOnly) {
            $resCol = @($resCol | Where-Object IsLatest -eq $false)
        }

        if ($AsExcelOutput) {
            $resCol | Export-Excel -NoNumberConversion Version, AvailableVersion, InstalledVersion, crmMinversion, crmMaxVersion, Version
            return
        }

        $resCol
    }
    
    end {
        
    }
}