O365Stuff.psm1

function Get-SharepointSiteOwner {
    <#
    .SYNOPSIS
    Get all Sharepoint sites and their owners.
    For O365 group sites, group owners will be outputted instead of the site one.
 
    .DESCRIPTION
    Get all Sharepoint sites and their owners.
    For O365 group sites, group owners will be outputted instead of the site one.
 
    .PARAMETER templateToIgnore
    List of site templates that will be ignored.
 
    By default:
    "SRCHCEN#0", "SPSMSITEHOST#0", "APPCATALOG#0", "POINTPUBLISHINGHUB#0", "EDISC#0", "STS#-1" (Search Center, Mysite Host, App Catalog, Content Type Hub, eDiscovery and Bot Sites)
 
    .EXAMPLE
    Connect-PnPOnline -Url "https://contoso.sharepoint.com" -Tenant 'contoso.onmicrosoft.com' -Credentials (Get-Credential)
 
    Get-SharepointSiteOwner
 
    Authenticate using user credentials and get all sites and their owners.
 
    .EXAMPLE
    Connect-PnPOnline -Url "https://contoso.sharepoint.com" -Tenant 'contoso.onmicrosoft.com' -ClientId 6c5c98c7-e05a-4a0f-bcfa-0cfc65aa1f28 -Thumbprint 34CFAA860E5FB8C44335A38A097C1E41EEA206AA
 
    Get-SharepointSiteOwner
 
    Authenticate using service principal (certificate) and get all sites and their owners.
 
    .EXAMPLE
    Connect-PnPOnline -Url "https://contoso.sharepoint.com" -Tenant 'contoso.onmicrosoft.com' -ClientId cd2ae428-35f9-41b4-a527-71f2f8f1e5cf -CertificatePath 'c:\appCert.pfx' -CertificatePassword (Read-Host -AsSecureString)
 
    Get-SharepointSiteOwner
 
    Authenticate using service principal (certificate) and get all sites and their owners.
 
    .NOTES
    Requires permissions: Sites.ReadWrite.All, Group.Read.All, User.Read.All
 
    https://www.sharepointdiary.com/2018/02/get-sharepoint-online-site-owner-using-powershell.html#ixzz7KCF1aDQ7
    https://www.sharepointdiary.com/2016/02/get-all-site-collections-in-sharepoint-online-using-powershell.html#ixzz7KDTA4xem
    #>


    [CmdletBinding()]
    param (
        [string[]] $templateToIgnore = @("SRCHCEN#0", "SPSMSITEHOST#0", "APPCATALOG#0", "POINTPUBLISHINGHUB#0", "EDISC#0", "STS#-1")
    )

    try {
        $null = Get-PnPConnection -ea Stop
    } catch {
        throw "You must call the Connect-PnPOnline cmdlet before calling any other cmdlets."
    }

    #Get All Site collections
    $SitesCollection = Get-PnPTenantSite | where Template -NotIn $templateToIgnore

    ForEach ($site in $sitesCollection) {
        $owner = $null
        Write-Verbose "Processing $($site.Url) site"

        if ($site.Template -like 'GROUP*') {
            #Get Group Owners
            try {
                Write-Verbose "`t- is group site, searching for group $($site.GroupId) owners"
                $owner = Get-PnPMicrosoft365GroupOwners -Identity ($site.GroupId) -ErrorAction Stop | % { if ($_.UserPrincipalName) { $_.UserPrincipalName } else { $_.Email } }
            } catch {
                if (($_ -match "does not exist or one of its queried reference-property objects are not present") -or ($_ -match "Group not found")) {
                    # group doesn't have any owner
                    $owner = "<<source group is missing>>"
                } else {
                    Write-Error $_
                }
            }
        } else {
            #Get Site Owner
            $owner = $site.Owner
        }

        [PSCustomObject]@{
            Site     = $site.Url
            Owner    = $owner
            Title    = $site.Title
            Template = $site.Template
        }
    }
}

function Remove-O365OrphanedMailbox {
    <#
    .SYNOPSIS
    Function for removal of O365 user mailbox for dir-synced user accounts that gets orphaned in AzureAD.
 
    .DESCRIPTION
    Function for removal of O365 user mailbox for dir-synced user accounts that gets orphaned in AzureAD.
 
    The function will:
    - move user account to OU that is not synchronized to AzureAD
    - initialize dir-sync, so the user account gets deleted in AzureAD
    - restore user in AzureAD, but now it is not dir-synced i.e. we can modify it in AzureAD
    - remove litigation hold settings
    - remove user mailbox
    - clear user connection-with-mailbox data
    - clear immutableId
    - move account to original OU
    - attach on-premises account with AzureAD account
 
    .PARAMETER samAccountName
    User samAccountName.
 
    .PARAMETER notSyncedOUDN
    Distinguished name of the OU that is NOT synchronized to your AzureAD.
 
    .EXAMPLE
    Remove-O365OrphanedMailbox -samAccountName JohnD -notSyncedOUDN "OU=notSynedToAAD,DC=contoso,DC=com"
 
    Fixes orphaned mailbox problem for user JohnD.
 
    .NOTES
    https://www.reddit.com/r/Office365/comments/mgfh1u/office_365_removing_litigation_hold_mailboxes_in/
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string] $samAccountName,

        [ValidateScript( {
                if (Get-ADOrganizationalUnit $_) {
                    $true
                } else {
                    throw "$_ is not a valid OU distinguished name. Enter distinguished name of the OU that is NOT synced into AzureAD."
                }
            })]
        [string] $notSyncedOUDN
    )

    if (!(Get-Module ActiveDirectory -ListAvailable)) {
        if ((Get-CimInstance win32_operatingsystem -Property caption).caption -match "server") {
            throw "Module ActiveDirectory is missing. Use: Install-WindowsFeature RSAT-AD-PowerShell -IncludeManagementTools" 
        } else {
            throw "Module ActiveDirectory is missing. Use: Get-WindowsCapability -Name RSAT* -Online | Add-WindowsCapability -Online"
        }
    }

    $userADObj = Get-ADUser $samAccountName -ErrorAction Stop

    $originalOU = ($userADObj.DistinguishedName -split ",")[1..1000] -join ','

    if ($userADObj.enabled) {
        throw "User $samAccountName is enabled. There is high probability you don't want to do this!"
    }

    $UPN = $userADObj.UserPrincipalName

    Connect-ExchangeOnline -ErrorAction Stop

    $null = Connect-MgGraph -Scopes User.ReadWrite.All -ea Stop

    $userAADObj = Get-MgUser -Filter "userPrincipalName eq '$UPN'"
    if (!$userAADObj) {
        throw "User $UPN doesn't exist in AAD"
    }

    # move account to NOT-AzureAD-synchronized OU
    "Moving user to '$notSyncedOUDN' OU (OU that MUST NOT be synchronized to AzureAD)"
    Move-ADObject -Identity $userADObj.ObjectGUID -TargetPath $notSyncedOUDN

    # synchronize these changes to AzureAD >> user should be deleted there automatically
    "Starting AzureAD directory sync"
    Start-AzureSync

    # wait for user deletion in AzureAD
    do {
        "..waiting for user $UPN removal in AzureAD"
        Start-Sleep 10
    } while (Get-MgUser -Filter "userPrincipalName eq '$UPN'")

    # restore deleted user
    "Restoring user"
    $null = Restore-MgDirectoryDeletedItem -DirectoryObjectId $userAADObj.Id

    # wait for user restoration in AzureAD
    do {
        "..waiting for (now not dir-synced) $UPN user restoration in AzureAD"
        Start-Sleep 10
    } while (!(Get-MgUser -Filter "userPrincipalName eq '$UPN'"))

    "Removing litigation hold settings"
    Set-mailbox -identity $UPN -removedelayholdapplied
    Set-mailbox -identity $UPN -removedelayreleaseholdapplied

    "..waiting for changes to apply"
    Start-Sleep 60

    # remove mailbox
    if (Get-Mailbox -identity $UPN -ErrorAction SilentlyContinue) {
        "Removing mailbox for $UPN"
        Disable-Mailbox -identity $UPN -permanentlyDisable -confirm:$false

        do {
            "..waiting for mailbox to disappear"
            Start-Sleep 10
        } while (Get-Mailbox -identity $UPN -ErrorAction SilentlyContinue)
    } else {
        "Mailbox for $UPN was removed"
    }

    #region steps to make sure mailbox won't be attached/recreated to this account again
    if ((Get-User -identity $UPN).PreviousRecipientTypeDetails -eq 'UserMailbox') {
        "Clearing connection to old mailbox"
        Set-user -identity $UPN -permanentlyclearpreviousmailboxinfo -confirm:$false
    }

    if ((Get-MgUser -Filter "userPrincipalName eq '$UPN'" -Property OnPremisesImmutableId).OnPremisesImmutableId) {
        "Clearing ImmutableId"
        $userId = (Get-MgUser -Filter "userPrincipalName eq '$UPN'" -Property Id).Id
        Invoke-MgGraphRequest -Method PATCH -Uri "https://graph.microsoft.com/v1.0/users/$userId" -Body @{OnPremisesImmutableId = $null }
    }
    #endregion steps to make sure mailbox won't be attached/recreated to this account again

    # move account back to original OU
    "Moving account back to $originalOU OU"
    Move-ADObject -Identity $userADObj.ObjectGUID -TargetPath $originalOU

    # synchronize these changes to AzureAD >> user should be deleted there automatically

    "Starting AzureAD directory sync, to 'attach' on-premises account with the AzureAD account representation"
    Start-AzureSync -type initial

    do {
        "..waiting for user to be attached"
        Start-Sleep 10
    } while (!(Get-MgUser -Filter "userPrincipalName eq '$UPN'"))
}

Export-ModuleMember -function Get-SharepointSiteOwner, Remove-O365OrphanedMailbox