Purge emails from a mailbox that are protected by a Organizational Hold


Scenario: You need to remove messages from a mailbox, but the mailbox is protected under an Organizational Hold (but it could also be a LitHold or a InPlaceHold). Since the message is currently protected, it will not purge out of the mailbox until the hold is expired, or ever, depending on what type of hold is on the mailbox.

Solution:

1. Disable the Hold:
You need to remove the mailbox from the Hold. If its an Organizational hold, add them to the exclusion. If its a LitHold or InPlace Hold, disable the hold.

get-mailbox steveman | Select *hold*
get-organizationconfig | Select *Hold*

2. Get the message to the Purges Folder:
Move the email items into the Purges folder. You can do this by any one of the following:

Manual Purging: Deleting the message from the mailbox within the client, then ‘deleting ‘purge’ the message from the Recoverable Deleted Items (Deletions folder).

Search Mailbox – You could use search-mailbox with -deletecontent -force.

New-ComplianceSearch & New-ComplianceSearchAction : You could use a new-compliancesearch with a new-compliancesearchaction -purge -purgetype HardDelete.

MFCMAPI: You could use MFCMAPI to move the message directly to the purges folder.

3. Disable SingleItemRecovery and RetainDeletedItemsFor:
Run the following:
set-mailbox steveman -singleitemrecoveryenabled:$false – retaindeleteditemsfor 0.00:00:00

4. Start the Mailbox Folder Assistant:
start-mailboxfolderassistant steveman

Note: For steps 3 and 4, give time in between the steps for Replication and settings to go into effect.

Error: Couldn’t find unique request with the provided information when get-mailboxexportrequest

Scenario: When running a get-mailboxexportrequest (or get-mailboximportrequest, remove-mailboxexportrequest, or remove-mailboximportrequest) you get the following error:

Couldn’t find a unique request with the provided information.

This is a result of running two separate mailbox export requests on the same mailbox, and Exchange didn’t create a unique name for each of the export request jobs. Therefore it creates a duplicate entry and Exchange hates you — just kidding.



Solution: If you need to cancel a duplicate export or import request, you can use the RequestGUID and RequestQueue to help identify the unique entry of a duplicate request, a true oxymoron.

get-mailboxexportrequest | Select Identity, name, RequestGuid, RequestQueue

Remove-mailboxexportrequest -RequestGuid xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx -RequestQueue DBxxxx

0x8024002e when adding AD Tools to your Windows 10 Computer

Scenario: When you try to install the AD Tools to your computer, you receive the error: 0x8024002e.

Solution:
In the registry, navigate to HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\Windows\WindowsUpdate and change the Dword value for DisableWindowsUpdateAccess from a 1 to a 0.
After a computer restart, you can attempt to run the install again in PowerShell:

Add-WindowsCapability -Name ‘Rsat.ActiveDirectory.DS-LDS.Tools~~~~0.0.1.0’ -Online


Hybrid Modern Authentication Checker

Scenario: If you are having issues with Hybrid Modern authentication in your Exchange On-Premises environment, feel free to use some of the logic below in the script.

Script: If Hybrid Modern authentication is already in place, feel free to start with the following:
1. Run the Test-OAUTHConnectivity command (Step 7)
2. Run the Hybrid Config Wizard should put the pieces in place necessary.
3. We had to perform step 3 due to an HMA interactive signin loop.



#Hybrid Modern Authentication Checker
#Steps expanded based on this article: https://docs.microsoft.com/en-us/exchange/configure-oauth-authentication-between-exchange-and-exchange-online-organizations-exchange-2013-help#step-5-register-all-hostname-authorities-for-your-internal-and-external-on-premises-exchange-http-endpoints-with-azure-active-directory

#1. Connect to Exchange On-Premises
        $n = "ACS","EvoSts"
        Get-AuthServer  | Where name -in $n
        #If both $severs exist,  its good!

#2. Make sure the Partner Application is Enabled
        Get-PartnerApplication |  ?{$_.ApplicationIdentifier -eq "00000002-0000-0ff1-ce00-000000000000" -and $_.Realm -eq ""}

#3. Identify the Current Certificate being used in the Get-AuthConfiguration for ServiceName 00000002-0000-0ff1-ce00-000000000000

        #Get Thumbprint
                $thumbprint = (Get-AuthConfig).CurrentCertificateThumbprint
        
        #Check Cert Expiration (NotAfter)
                Get-ExchangeCertificate -Thumbprint $thumbprint | Select Thumbprint, Services, Subject,NotAfter

        #Check to see what certificate is being used in Microsoft Online, Verify with the StartDate/EndDate
                Connect-msolservice 
                Get-MsolServicePrincipalCredential -ServicePrincipalName "00000002-0000-0ff1-ce00-000000000000" -ReturnKeyValues $true |Select Type, KeyID, Startdate,Enddate

        #If we are receiving a Modern Auth Loop, Run the following to export and re-import the certificate:
            #Export from On-Premises
                $thumbprint = (Get-AuthConfig).CurrentCertificateThumbprint
                if((test-path $env:SYSTEMDRIVE\OAuthConfig) -eq $false)
                {
                    md $env:SYSTEMDRIVE\OAuthConfig
                }
                cd $env:SYSTEMDRIVE\OAuthConfig
                $oAuthCert = (dir Cert:\LocalMachine\My) | where {$_.Thumbprint -match $thumbprint}
                $certType = [System.Security.Cryptography.X509Certificates.X509ContentType]::Cert
                $certBytes = $oAuthCert.Export($certType)
                $CertFile = "$env:SYSTEMDRIVE\OAuthConfig\OAuthCert.cer"
                [System.IO.File]::WriteAllBytes($CertFile, $certBytes)

            #Import into Microsoft Online 
                Connect-MsolService
                $CertFile = "$env:SYSTEMDRIVE\OAuthConfig\OAuthCert.cer"
                $objFSO = New-Object -ComObject Scripting.FileSystemObject
                $CertFile = $objFSO.GetAbsolutePathName($CertFile)
                $cer = New-Object System.Security.Cryptography.X509Certificates.X509Certificate
                $cer.Import($CertFile)
                $binCert = $cer.GetRawCertData()
                $credValue = [System.Convert]::ToBase64String($binCert)
                $ServiceName = "00000002-0000-0ff1-ce00-000000000000"
                $p = Get-MsolServicePrincipal -ServicePrincipalName $ServiceName
                New-MsolServicePrincipalCredential -AppPrincipalId $p.AppPrincipalId -Type asymmetric -Usage Verify -Value $credValue

            #Verify the new certificate is there:
                Connect-MsolService
                Get-MsolServicePrincipalCredential -ServicePrincipalName "00000002-0000-0ff1-ce00-000000000000" -ReturnKeyValues $true


#4. SPN Check:
    #Verify the namespaces used in Exchange On-Prmeises are all present and registered as SPNs in Microsoft Online 
        Get-MapiVirtualDirectory | FL server,*url*
        Get-WebServicesVirtualDirectory | FL server,*url*
        Get-ClientAccessServer | fl Name, AutodiscoverServiceInternalUri
        Get-OABVirtualDirectory | FL server,*url*
        Get-AutodiscoverVirtualDirectory | FL server,*url*
        Get-OutlookAnywhere | FL server,*url*

    #Verify the SPNs (urls) are listed in Microsoft for 00000002-0000-0ff1-ce00-000000000000
        Get-MsolServicePrincipal -AppPrincipalId 00000002-0000-0ff1-ce00-000000000000 | select -ExpandProperty ServicePrincipalNames

    #If you need to add a SPN because a new namespace is being used, or if a namespace is missing:
        $ServiceName = "00000002-0000-0ff1-ce00-000000000000";
        $x = Get-MsolServicePrincipal -AppPrincipalId $ServiceName;
        $x.ServicePrincipalnames.Add("https://mail.contoso.com/");
        $x.ServicePrincipalnames.Add("https://autodiscover.contoso.com/");
        Set-MSOLServicePrincipal -AppPrincipalId $ServiceName -ServicePrincipalNames $x.ServicePrincipalNames;


#5. Verify the IntraOrganizationConnector in Exchange On-Premises
            Get-IntraOrganizationConnector

        #If you need to add it:
            $ServiceDomain = Get-AcceptedDomain | where {$_.DomainName -like "*.mail.onmicrosoft.com"} | select -ExpandProperty Name
            New-IntraOrganizationConnector -name ExchangeHybridOnPremisesToOnline -DiscoveryEndpoint https://outlook.office365.com/autodiscover/autodiscover.svc -TargetAddressDomains $ServiceDomain



#6. Verify the IntraOrganizationConnector in Exchange Online
            get-intraorganizationconnector

            #If you need to add it
            New-IntraOrganizationConnector -name ExchangeHybridOnlineToOnPremises -DiscoveryEndpoint <your on-premises Autodiscover endpoint> -TargetAddressDomains <your on-premises SMTP domain>


#7. Test Oauth Connectivity

        #From Exchange Online PowerShell
            test-OAuthConnectivity -Service EWS -TargetUri https://<OnPremNamespace>/metadata/json/1 -Mailbox <ExOnline MBX UPN> -Verbose
        
        #From Exchange On-Premises PowerShell
            test-OAuthConnectivity -Service EWS -TargetUri https://outlook.office365.com/ews/exchange.asmx -Mailbox  -Verbose


AADSTS500011: The resource principal named was not found in the tenant

Scenario: If your receive the following error when using Hybrid Modern Authentication for Exchange OnPremises, run the following scriptlet below. Our problem was a misconfigured URL set on the get-clientaccessserver where it was pointing to a Servername, and not our shared namespace.

Scriptlet:

 Get-MapiVirtualDirectory | FL server,*url*
 Get-WebServicesVirtualDirectory | FL server,*url*
 Get-ClientAccessServer | fl Name, AutodiscoverServiceInternalUri
 Get-OABVirtualDirectory | FL server,*url*
 Get-AutodiscoverVirtualDirectory | FL server,*url*
 Get-OutlookAnywhere | FL server,*url*

Graph API – Search AuditLogs\SignIns for users to see who has used MFA within 30 Days


Scenario: You want to query the Azure AD SignIn Logs to see who has used MFA within the last 30 days via Graph and PowerShell. You have the Userprincipalnames in a CSV already.

Scriptlet:
Notes:
1. It performs a get-accesstoken function which can be found and loaded from this blog: Get an Access Token for Graph API via PowerShell – Ex-Shell


   #Declare global variables
   $i = import-csv "C:\temp\userprincipalnames.csv"  #With Userprincipalname as the column header 
   $start = get-date((get-date).adddays(-30)) -Format "yyyy-MM-dd"

   #Loop it
    $i.userprincipalname | Sort | %{
        #Declare UPN
        $n = $_
        "Checking $N"

         #Build the URI   
         $appuri = "https://graph.microsoft.com/v1.0/auditlogs/signIns?$('$filter')=(userprincipalname eq '$n') and (createdDateTime ge $start)"
         $appuri = ([System.Uri]$appuri).AbsoluteUri

        #Get the token and create the RestSplat
        $header = get-accesstoken 
        $results = @()
        $RestSplat = @{ 
            URI         = $appuri
            Headers     = $header
            Method      = 'GET' 
            ContentType = "application/json" 
            } 

        #Invoke the Rest URI
        $Tempresults =  Invoke-RestMethod @RestSplat


        #Play with results
            #MFA check    
            $Tempresults.value.appliedConditionalAccessPolicies | Where {($_.result -eq "Success") -and ($_.enforcedGrantControls -like "*MFA*")}

            #Signin at all?
           $Tempresults.value

    }


Cannot delete a Exchange Database because “This mailbox database contains one or more mailboxes, mailbox plans, archive mailboxes, public folder mailboxes or arbitration mailboxes, Audit mailboxes”

Scenario: When attempting to remove an Exchange database, you receive the following error:

This mailbox database contains one or more mailboxes, mailbox plans, archive mailboxes, public folder mailboxes or
arbitration mailboxes, Audit mailboxes.


Scriptlet: Here is a quick script to check that database for mailbox or mailbox data that may be active on it:

$Db = “DB01”
Get-Mailbox -Database $DB
Get-MailboxPlan
Get-Mailbox -Database $DB -Archive
Get-Mailbox -Database $DB -PublicFolder
Get-Mailbox -Database $DB -Arbitration.
Get-Mailbox -Database $DB -AuditLog
Get-MoveRequest |Where {($_.TargetDatabase -eq $db) -or ($_.SourceDatabase -eq $DB)}



#GoRavens

Check Microsoft, Security and Compliance, Azure, and Exchange Role Membership

Scenario: You want to quickly gather membership information on all of Microsoft Online, AzureAD, and Exchange Online Roles.

Scriptlets:

Check Microsoft Online for Role Membership

#Connect to Microsoft Online: Connect-MSOLService
$MSrole_user = @()
$MSroles = Get-MsolRole
$MSroles | Sort name | %{
$n = $_.name
“Checking Role: $n”
$MSrole_User += Get-MsolRoleMember -RoleObjectId $_.ObjectId | Select @{Name=”RoleName”;Expression={“$n”}},DisplayName, EmailAddress,RoleMemberType
}
$MSRole_User


Check Azure AD for Role Membership

#Connect to Azure AD: Connect-AzureAD
$AZrole_user = @()
$AZroles = Get-AzureADDirectoryRole
$AZroles | Sort DisplayName | %{
$n = $_.DisplayName
“Checking Role: $n”
$AZrole_User += Get-AzureADDirectoryRoleMember -ObjectId $_.ObjectId | Select @{Name=”RoleName”;Expression={“$n”}},DisplayName,ObjectType,Mail,SecurityEnabled
}
$AZRole_User

Check for Security and Compliance Role Membership

#Connect to IPPS Sessions Online: Connect-IPPSSession
$SCrole_user = @()
$SCroles = Get-RoleGroup
$SCRoles | Sort Name | %{
$n = $_.name
“Checking Role: $n”
$SCRole_User += get-rolegroupmember $N | select @{Name=”RoleName”;Expression={“$N”},name,windowsliveid
}
$SCRole_User

Check Exchange Online Online

#Connect to Exchange Online: Connect-ExchangeOnline
$Exchange_Roles = get-managementroleassignment -geteffectiveusers
$exchange_Roles_Unique = $Exchange_Roles | Select RoleAssigneeName,EffectiveUserName
$Exchange_Roles_Unique = $exchange_Roles_Unique | Select -Unique RoleAssigneeName,EffectiveUserName
$Exchange_Roles_Unique

Another DoWhile Loop method for performing an action on large datasets in smaller subsets

Scenario: You have a large data set, maybe a large amount of mailboxes, and you need to set the RoleAssignmentPolicy to a policy that does not allow email forwarding.

Solution:

Collect Mailboxes

$mbx = get-mailbox -resultsize unlimited | where RoleAssignmentPolicy -ne PolicyWithNoEmailForward

Create DoWhile Variables

$start = 0
$inc = 10
$end = $inc
$totalCount = $mbx.count

Loop It

Do{
“Running on $start..$end out of $totalCount”
$mbx[$start..$end] | Set-mailbox -RoleAssignmentPolicy PolicyWithNoEmailForward
$start = $start + $inc
$end = $end + $inc
}While($start -le $totalCount)

Use Paging within a Select statement when running PowerShell commands to avoid Throttling

Scenario: You have a large dataset that you want to use a Do-While Loop with a Paging system to separate the data into smaller data sets. In this example, I will use a Do-While loop to set a roleassignmentpolicy against may mailboxes:

Solution: Run the following commands

Collect the Mailboxes

$mbx = get-mailbox -resultsize unlimited | where RoleAssignmentPolicy -ne “PolicyWithNoEmailForward”

Create the Page Variables

$total_Count = $mbx.count
$Sleep = 60 #in seconds
$page = 10 #the amount of records you want to go through per set
$page_Count = $page #The amount the page will increase

Buffer for DoWhile Loop

$mbx | Select -first $page | %{
$N = $_.name
“Setting RoleAssignmentPolicy on Mbx: $N”
Set-mailbox $n -roleassignmentPolicy “PolicyWithNoEmailForward”
}

Run the DoWhile Loop

Do{

#Run through the first set of Mailboxes
$mbx | Select -first $page -skip $page | %{
    $N = $_.name
    "Setting RoleAssignmentPolicy on Mbx: $N" 
    Set-mailbox $n -roleassignmentPolicy "PolicyWithNoEmailForward"
  }

#Display a separator between each set
 "

  Next"

#Increase the Page and SKip Count
  $Page = $page + $page_Count

#Sleep Duration
  Sleep $Sleep

} While($Page -lt $Total_Count)