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.
Category: Powershell
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)