core/api/m365/SharePointOnline/csom/helpers/web/Get-MonkeyCSOMOrphanedIdentity.ps1

# Monkey365 - the PowerShell Cloud Security Tool for Azure and Microsoft 365 (copyright 2022) by Juan Garrido
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


Function Get-MonkeyCSOMOrphanedIdentity{
    <#
        .SYNOPSIS
        Get orphaned users and groups from Sharepoint Online
 
        .DESCRIPTION
        Get orphaned users and groups from Sharepoint Online
 
        .INPUTS
 
        .OUTPUTS
 
        .EXAMPLE
 
        .NOTES
            Author : Juan Garrido
            Twitter : @tr1ana
            File Name : Get-MonkeyCSOMOrphanedIdentity
            Version : 1.0
 
        .LINK
            https://github.com/silverhack/monkey365
    #>

    [CmdletBinding(DefaultParameterSetName = 'Current')]
    [OutputType([System.Collections.Generic.List[System.Management.Automation.PSObject]])]
    Param (
        [Parameter(Mandatory= $false, ParameterSetName = 'Web', ValueFromPipeline = $true, ValueFromPipelineByPropertyName  = $true, HelpMessage="SharePoint Web Object")]
        [Object]$Web,

        [parameter(Mandatory= $false, HelpMessage="Authentication Object")]
        [Object]$Authentication,

        [parameter(Mandatory= $false, ParameterSetName = 'Endpoint', HelpMessage="SharePoint url")]
        [String]$Endpoint
    )
    Begin{
        #Set null
        $all_users = $orphanedIds = $null;
        #body data
        [xml]$body_data = '<Request AddExpandoFieldTypeSuffix="true" SchemaVersion="15.0.0.0" LibraryVersion="16.0.0.0" ApplicationName="Monkey365" xmlns="http://schemas.microsoft.com/sharepoint/clientquery/2009"><Actions><Query Id="52" ObjectPathId="5"><Query SelectAllProperties="false"><Properties/></Query><ChildItemQuery SelectAllProperties="false"><Properties><Property Name="Id" ScalarProperty="true"/><Property Name="Title" ScalarProperty="true"/><Property Name="LoginName" ScalarProperty="true"/><Property Name="Email" ScalarProperty="true"/><Property Name="IsShareByEmailGuestUser" ScalarProperty="true"/><Property Name="IsSiteAdmin" ScalarProperty="true"/><Property Name="UserId" ScalarProperty="true"/><Property Name="IsHiddenInUI" ScalarProperty="true"/><Property Name="PrincipalType" ScalarProperty="true"/><Property Name="AadObjectId" ScalarProperty="true"/><Property Name="UserPrincipalName" ScalarProperty="true"/><Property Name="Alerts"><Query SelectAllProperties="false"><Properties/></Query><ChildItemQuery SelectAllProperties="false"><Properties><Property Name="Title" ScalarProperty="true"/><Property Name="Status" ScalarProperty="true"/></Properties></ChildItemQuery></Property><Property Name="Groups"><Query SelectAllProperties="false"><Properties/></Query><ChildItemQuery SelectAllProperties="false"><Properties><Property Name="Id" ScalarProperty="true"/><Property Name="Title" ScalarProperty="true"/><Property Name="LoginName" ScalarProperty="true"/></Properties></ChildItemQuery></Property></Properties></ChildItemQuery></Query></Actions><ObjectPaths><Property Id="5" ParentId="3" Name="SiteUsers"/><Property Id="3" ParentId="1" Name="Web"/><StaticProperty Id="1" TypeId="{3747adcd-a3c3-41b9-bfab-4a64dd2f1e0a}" Name="Current"/></ObjectPaths></Request>'
    }
    Process{
        try{
            If($O365Object.canRequestGroupsFromMsGraph -and $O365Object.canRequestUsersFromMsGraph){
                If($PSCmdlet.ParameterSetName -eq "Endpoint" -or $PSCmdlet.ParameterSetName -eq "Current"){
                    $_Web = Get-MonkeyCSOMWeb @PSBoundParameters
                    if($null -ne $_Web){
                        #Remove Endpoint if exists
                        [void]$PSBoundParameters.Remove('Endpoint')
                        $_Web | Get-MonkeyCSOMOrphanedIdentity @PSBoundParameters
                        return
                    }
                }
                Foreach($_Web in @($PSBoundParameters['Web'])){
                    #Set array
                    $batchObjects = [System.Collections.Generic.List[System.Object]]::new()
                    #Set generic list
                    $orphanedIdentities = [System.Collections.Generic.List[System.Object]]::new()
                    $msg = @{
                        MessageData = ($message.SPSCheckSiteForOrphanedObjects -f $_Web.Url);
                        callStack = (Get-PSCallStack | Select-Object -First 1);
                        logLevel = 'info';
                        InformationAction = $O365Object.InformationAction;
                        Tags = @('MonkeyCSOMOrphanedObjectInfo');
                    }
                    Write-Information @msg
                    #Set command parameters
                    $p = Set-CommandParameter -Command "Invoke-MonkeyCSOMRequest" -Params $PSBoundParameters
                    #Add authentication header if missing
                    if(!$p.ContainsKey('Authentication')){
                        if($null -ne $O365Object.auth_tokens.SharePointOnline){
                            [void]$p.Add('Authentication',$O365Object.auth_tokens.SharePointOnline);
                        }
                        Else{
                            Write-Warning -Message ($message.NullAuthenticationDetected -f "SharePoint Online")
                            break
                        }
                    }
                    #Add endpoint
                    [void]$p.Add('Endpoint',$_Web.Url);
                    #Add post Data
                    [void]$p.Add('Data',$body_data);
                    #Add ChidItems
                    [void]$p.Add('ChildItems',$true);
                    #Execute query
                    $all_users = Invoke-MonkeyCSOMRequest @p
                    $all_users = @($all_users).Where({
                        $_.Title.ToLower() -ne "everyone"`
                             -and $_.Title.ToLower() -ne "everyone except external users"`
                             -and $_.Title.ToLower() -ne "sharepoint app"`
                             -and $_.Title.ToLower() -ne "system account"`
                             -and $_.Title.ToLower() -ne "nt service\spsearch"`
                             -and $_.Title.ToLower() -ne "sharepoint service administrator"`
                             -and $_.Title.ToLower() -ne "global administrator"`
                            -and $_.Title.ToLower() -ne "all users (windows)"`
                            -and $_.Title.ToLower() -ne "guest contributor"
                    });
                    #Filter for all users
                    $all_objects = @($all_users).Where({$_.principalType -eq [principalType]::User -or ($_.loginName -match 'c:0t.c' -or $_.loginName -match 'c:0o.c')})
                    #Get Object Id
                    $_users = $all_objects.Where({$null -ne $_.AadObjectId})
                    $objectIds = $_users.AadObjectId.nameId
                    $objectIds | Split-Array -Elements 1000 | ForEach-Object {
                        $jp = @{
                            ScriptBlock = { Get-MonkeyMSGraphDirectoryObjectById -Ids $_ -APIVersion beta};
                            InputObject = $_;
                            Runspacepool = $O365Object.monkey_runspacePool;
                            ReuseRunspacePool = $true;
                            Debug = $O365Object.VerboseOptions.Debug;
                            Verbose = $O365Object.VerboseOptions.Verbose;
                            MaxQueue = $O365Object.nestedRunspaces.MaxQueue;
                            BatchSleep = $O365Object.nestedRunspaces.BatchSleep;
                            BatchSize = $O365Object.nestedRunspaces.BatchSize;
                        }
                        #Request Entra ID objects
                        $EntraObjects = Invoke-MonkeyJob @jp
                        if($null -eq $EntraObjects){
                            $msg = @{
                                MessageData = "An empty response was received from Entra Id";
                                callStack = (Get-PSCallStack | Select-Object -First 1);
                                logLevel = 'Verbose';
                                InformationAction = $O365Object.InformationAction;
                                Verbose = $O365Object.verbose;
                                Tags = @('Monkey365EnraIdEmptyResponse');
                            }
                            Write-Verbose @msg
                        }
                        ElseIf ($EntraObjects -is [System.Collections.IEnumerable] -and $EntraObjects -isnot [string]){
                            [void]$batchObjects.AddRange($EntraObjects);
                        }
                        ElseIf ($EntraObjects.GetType() -eq [System.Management.Automation.PSCustomObject] -or $EntraObjects.GetType() -eq [System.Management.Automation.PSObject]) {
                            [void]$batchObjects.Add($EntraObjects);
                        }
                        Else{
                            $msg = @{
                                MessageData = "Unable to recognize object from Entra Id";
                                callStack = (Get-PSCallStack | Select-Object -First 1);
                                logLevel = 'Verbose';
                                InformationAction = $O365Object.InformationAction;
                                Verbose = $O365Object.verbose;
                                Tags = @('Monkey365UnrecognizedEnraIdObject');
                            }
                            Write-Verbose @msg
                        }
                    }
                    #Get object Id
                    $entraObjectIds = $batchObjects | Select-Object -ExpandProperty Id -ErrorAction Ignore
                    $disabledUsersIds = $batchObjects.Where({$_."@odata.type" -match '#microsoft.graph.user' -and $_.accountEnabled -eq $false}) | Select-Object -ExpandProperty Id -ErrorAction Ignore
                    #Check if orphaned Ids
                    if($entraObjectIds){
                        $orphanedIds = Compare-Object -ReferenceObject $objectIds -DifferenceObject $entraObjectIds | Select-Object -ExpandProperty InputObject
                    }
                    #Get orphaned users
                    if($null -ne $orphanedIds -and @($orphanedIds).Count -gt 0){
                        @($_users).Where({$_.AadObjectId.nameId -in $orphanedIds}) | ForEach-Object {
                            $_ | Add-Member NoteProperty -Name SiteUrl -Value $_Web.Url -Force
                            $_ | Add-Member NoteProperty -Name orphanedType -Value "deleted" -Force
                            [void]$orphanedIdentities.Add($_);
                        }
                    }
                    if($null -ne $disabledUsersIds -and @($disabledUsersIds).Count -gt 0){
                        @($_users).Where({$_.AadObjectId.nameId -in $disabledUsersIds}) | ForEach-Object {
                            $_ | Add-Member NoteProperty -Name SiteUrl -Value $_Web.Url -Force
                            $_ | Add-Member NoteProperty -Name orphanedType -Value "disabled" -Force
                            [void]$orphanedIdentities.Add($_);
                        }
                    }
                    #Check for null nameId
                    $_users = @($all_objects).Where({$null -eq $_.AadObjectId})
                    if($_users.Count -gt 0){
                        $_users.foreach({
                            $_ | Add-Member NoteProperty -Name SiteUrl -Value $_Web.Url -Force
                            $_ | Add-Member NoteProperty -Name orphanedType -Value "deleted" -Force
                            [void]$orphanedIdentities.Add($_);
                        });
                    }
                }
                Write-Output $orphanedIdentities -NoEnumerate
            }
            Else{
                $msg = @{
                    MessageData = ($message.GraphV2ErrorMessage);
                    callStack = (Get-PSCallStack | Select-Object -First 1);
                    logLevel = 'Warning';
                    InformationAction = $O365Object.InformationAction;
                    Tags = @('Monkey365GraphV2Error');
                }
                Write-Warning @msg
            }
        }
        Catch{
            Write-Error $_
        }
    }
    End{
    }
}