Framework/Core/AzSKInfo/SPNInfo.ps1

using namespace System.Management.Automation
Set-StrictMode -Version Latest 

class SPNInfo: CommandBase
{    
    hidden $GraphOwnedObjectsAPIUri = [string]::Empty
    hidden $ApiBaseEndpoint = [string]::Empty
    SPNInfo([string] $subscriptionId, [InvocationInfo] $invocationContext): 
        Base($subscriptionId, $invocationContext) 
    { 
        $this.DoNotOpenOutputFolder = $true;
        $this.GraphOwnedObjectsAPIUri = 'https://graph.microsoft.com/v1.0/me/ownedObjects';
        $this.ApiBaseEndpoint = [ConfigurationManager]::GetAzSKConfigData().AzSKApiBaseURL;
    }
    
    GetSPNInfo()
    {
        if([string]::IsNullOrEmpty($this.ApiBaseEndpoint))
        {
            #This feature is currently available only for org in which Endpoint/backend API url is configured
            $this.PublishCustomMessage("`r`nThis feature is currently not available for your environment.`r`n", [MessageType]::Warning)
            return
        }
        $ownedSPNs = @()
        $usedSPNs = @()
        $notInUsedSPNs = @()
        $this.PublishCustomMessage([Constants]::DoubleDashLine + "`r`nFetching SPN(s) Details...`r`n" + [Constants]::DoubleDashLine);
        #Get all owned SPNs
        $ownedSPNDetails = $this.GetOwnedSPNList();
        try
        {
            #Get SPNs start with AzSK_CA
            if($null -ne $ownedSPNDetails -and ($ownedSPNDetails | Measure-Object).Count -gt 0)
            {
                #Filter OwnedSPN start with AzSK_CA or AzSDK_CA
                $ownedSPNs += $ownedSPNDetails | Where-Object { ($_.displayName -like "AzSK_CA*") -or ($_.displayName -like "AzSDK_CA*")}
            }
            
            if($null -ne $ownedSPNs -and ($ownedSPNs | Measure-Object).Count -gt 0)
            {
                #Get SPNsResponse which contain list of used SPNs
                $SPNsResponse = [RemoteApiHelper]::FetchUsedSPNList(@($ownedSPNs.appId));
                #Convert SPNsResponse into usedSPNs, which contain list of CA used SPNs (active SPNs)
                if(($null -ne $SPNsResponse) -and ($SPNsResponse -ne "ERROR") -and ( ($SPNsResponse | Get-Member StatusCode) -and  $SPNsResponse.StatusCode -eq 202))
                {
                    $SPNsResponse | ConvertFrom-Json | where-object { $usedSPNs += $_ }
                    #Get list of notInUsedSPNs, which contain list of SPNs which are currently not being used by CA
                    if(($null -ne $usedSPNs) -and ($usedSPNs | Measure-Object).Count -ne 0)
                    {
                        # Get display name of the application id
                        $usedSPNs | ForEach-Object { 
                            
                            $appId = $_.appId;
                            $displayName = ($ownedSPNs | where-object { $appId -eq $_.appId } | Select-Object displayName).displayName;
                            $_ | Add-Member -MemberType NoteProperty -Name displayName -Value $displayName;
                        }

                        # Filter list of unused SPNs
                        $notInUsedSPNs = $ownedSPNs | where-object { $_.appId -notin $usedSPNs.appId }
                    }
                    else 
                    {
                        $notInUsedSPNs = $ownedSPNs;
                    }
                    
                    $this.PublishCustomMessage("`r`nSPN(s) owned by you which are currently being used by CA:`r`n",[MessageType]::Default)
                    #Adding blank line
                    write-host ""
                        
                    if(($usedSPNs | Measure-Object).Count -eq 0)
                    {
                        $this.PublishCustomMessage("`r`n`r`nCurrently there is no SPN owned by you, which is being used by CA.", [MessageType]::Warning);
                        #Adding blank line
                        write-host ""
                    }
                    else
                    {
                        $this.PublishCustomMessage($($usedSPNs | Format-Table @{Label = "ApplicationId"; Expression = { $_.appId } }, @{Label = "DisplayName"; Expression = { $_.displayName } },@{Label = "SubscriptionId"; Expression = { $_.subscriptionId }},@{Label = "SubscriptionName"; Expression = { $_.subscriptionName } } -AutoSize -Wrap | Out-String), [MessageType]::Default)
                        #Adding blank line
                        write-host ""
                    }
            
                    $this.PublishCustomMessage([Constants]::SingleDashLine, [MessageType]::Default);
            
                    $this.PublishCustomMessage("`r`nSPN(s) owned by you which have not been used by CA in last 7 days:`r`n",[MessageType]::Default)
                    #Adding blank line
                    write-host ""
                    $this.PublishCustomMessage($($NotInUsedSPNs | Format-Table @{Label = "ApplicationId"; Expression = { $_.appId }},@{Label = "DisplayName"; Expression = { $_.displayName } } -AutoSize | Out-String), [MessageType]::Default);
                }
                else
                {
                    $this.PublishCustomMessage("`r`nUnable to validate SPN(s) details. StatusMessage [$($SPNsResponse)]`r`n", [MessageType]::Error)
                }
                
            }
            else
            {
                $this.PublishCustomMessage("`r`nCurrently there is no SPN owned by you, which is being used by CA.", [MessageType]::Warning);
            }
        }
        catch
        {
            $ExceptionMessage = $_;
            if ([Helpers]::CheckMember($_,"Exception.Message"))
            {
                $ExceptionMessage = $_.Exception.Message.ToString()
            }
            
            $this.PublishCustomMessage("`r`nUnable to validate SPN(s) details. StatusMessage [$($ExceptionMessage)]`r`n", [MessageType]::Error)
        }
    }

    # This function returns the list of AD application owned by current user
    [PSObject] GetOwnedSPNList()
    {
        # TODO: Move this to control setting file
        $filterOwnedObjectsType = @("#microsoft.graph.application", "#microsoft.graph.servicePrincipal");
        $ownedSPNDetails = @()
        $result = ""
        try
        {
            $ResourceAppIdURI = [WebRequestHelper]::GetADGraphURL()
            $accessToken = [ContextHelper]::GetAccessToken($ResourceAppIdURI)
            $header = "Bearer " + $accessToken
            $headers = @{"Authorization"=$header;"Content-Type"="application/json";}
            $uri=$this.GraphOwnedObjectsAPIUri;
            
            # @odata.nextLink handled in the web request
            $responseContent = [WebRequestHelper]::InvokeWebRequest([Microsoft.PowerShell.Commands.WebRequestMethod]::Get, $uri, $headers, $null, [string]::Empty, @{})
            #Check if no owned SPN found
            if(($null -ne $responseContent) -and ($responseContent | get-member -Name "@odata.type") -and ($responseContent."@odata.type"))
            {
                $ownedSPNDetails = $responseContent | Where-Object { $filterOwnedObjectsType -contains $_."@odata.type"} | Select-Object -Property displayName,appId -Unique 
            }
            
        }
        catch 
        {    
            # Exception get handle in base class
            throw $_
        }
        return @($ownedSPNDetails)
    } 

}