Remotely trigger delta AD Connect sync!

How often do you RDP to the AD Connect server to run a Delta Sync?

Yes I know, quite often right? And that is only once you find out which server it is running on. Especially If you are in new environments a lot or someone moved it since last time… sheesh thanks for telling us Dave!! 😭🤣

This script can be run from any Windows 10, 2016 or later endpoint… it will attempt to get the servername from AD then connect remotely and run a delta sync (we do some checks and have some messaging if things fail).

🙂👍 🙂

NOTE: Update 13/12/21 – when finding the AD Connect server, if you’ve already had more than one and someone hasn’t deleted the old computer account, both names will be returned causing the script to fail. Just run that bit of code first and delete any old accounts from the domain (or just replace the code with the server name)

First thing we do is run the following commands in an elevated PowerShell prompt to add the AD PowerShell module:

Install-PackageProvider Nuget -Force #justbecause

For Windows 10/11:
Add-WindowsCapability –online –Name Rsat.ActiveDirectory.DS-LDS.Tools~~~~0.0.1.0

For Windows 2016/2019:
Install-WindowsFeature RSAT-AD-PowerShell -Confirm:$false

Next, let’s make this easy to run with elevated rights by copying the script text into notepad and saving it into the c:\_scripts folder as “Force AD Connect Sync.ps1”

Then create a “Force AD Connect Sync.cmd” on your desktop with the following in it:

start powershell.exe -ExecutionPolicy Bypass -File "c:\_scripts\Force AD Connect Sync.ps1"

Now we can right-click on the cmd file and click ‘Run as Administrator”. Does the trick and time is life!

To find the server we use a method from easy365manager, and the link for enabling remoting is from faqforge. Thanks peoples!

Easy365manager:
https://www.easy365manager.com/how-to-identify-your-azure-ad-connect-server/

Faqforge:
https://www.faqforge.com/windows/create-powershell-session-remote-computer/

Here is the script – let me know if it worked or if it sucked and how you made it better! Until next time! Cheers, Simon 🍺 …oh and PS – if you want a great rundown on AD Connect, check out Adam’s post:

https://adamtheautomator.com/azure-ad-connect/#Install_Azure_AD_Connect

# force a delta sync to Azure AD

# load AD module
Try {
    Import-Module ActiveDirectory
}
    Catch {
        Write-Warning "Encountered a problem importing AD module."
        Write-Host
        Read-Host "Press Enter to exit..."
        Exit
    }
Write-Host -ForegroundColor Green "AD module loaded successfully."
Write-Host

Try {
    $ADConnectServer = Get-ADUser -LDAPFilter "(description=*configured to synchronize to tenant*)" -Properties description | % { $_.description.SubString(142, $_.description.IndexOf(" ", 142) - 142)}
}
    Catch {
        Write-Warning "Encountered a problem obtaining name of AD Connect server."
        Write-Host
        Read-Host "Press Enter to exit..."
        Exit
    }

Write-Host -ForegroundColor Green "Found AD Connect server $ADConnectServer!  Testing connection..."
Write-Host

Try {
    $session = New-PSSession -ComputerName $ADConnectServer -Authentication Default
    Enter-PSSession $ADConnectServer
}
    Catch {
        Write-Warning "Cannot connect to $ADConnectServer, please check remote connectivity." 
        Write-Warning "ref - https://www.faqforge.com/windows/create-powershell-session-remote-computer/"
        Write-Host
        Read-Host "Press Enter to exit..."
        Exit
    }

Write-Host -ForegroundColor Green "Connected to $ADConnectServer - Forcing a delta sync... one moment!"
Write-Host

Try {
    Start-ADSyncSyncCycle -PolicyType Delta
}
    Catch {
        Write-Warning "The command failed - either a sync is already in progress," 
        Write-Warning "or you are not a member of the 'ADSyncAdmins' group on the AD Connect server."
        Write-Host
        Read-Host "Press Enter to exit..."
        Exit
    }

Write-Host -ForegroundColor Green "Sync started successfully!"
Write-Host
Read-Host "Press Enter to exit..."

# clean up
Exit-PSSession
Remove-PSSession $session

Remove proxy address for specific domain from Exchange Distribution groups

This script can be run after connecting to Exchange Online or on-premises environment. Replace “porkchops.com” with the suffix you want to remove 👍

Thanks to me mate Sailesh who loooves his porkchops!! 🤣🤣

# Remove proxy address for "porkchops.com" from Exchange Distribution groups

$domainname = "porkchops.com"

$groups = Get-DistributionGroup -Resultsize unlimited | where {$_.EmailAddresses -like "*$domainname*"} 

foreach ($group in $groups) {  

    $groupidentity = $group.identity
    $addresstoremove = $group.Alias+"@$domainname"

    Set-DistributionGroup $groupidentity -EmailAddresses @{remove=$addresstoremove}
}

# End

How to restrict partner access to Azure and 365

Most customers have accepted at least one, if not multiple invitations from Microsoft partners to provide licensing or support services. What they often dont know is that by default this allows the partner to assign full administrative access to any of it’s staff, to perform tasks in the customers 365 / Azure tenant. It’s an ‘all or nothing’ configuration which is, and should be of concern to many customers who read the fine print of the invitation they are accepting.

The recent hack on a large distributor highlights how dangerous leaving this ‘as is’ can be:

Mega-distie SYNNEX attacked and Microsoft cloud accounts it tends tampered • The Register

Microsoft are developing the Lighthouse solution to allow us to use more detailed permissions for support. But it’s not there yet, so I started testing another solution to ths problem.

Turns out you can do most things using B2B guest access and client targeted URLs (appending the clients custom domain to the admin URL) as below:

Helpdesk staff use:
Azure AD – https://aad.portal.azure.com/customer.com
Exchange – https://outlook.office365.com/ecp/@customer.onmicrosoft.com

Admin staff use:
Azure – https://portal.azure.com/customer.com
Exchange – https://outlook.office365.com/ecp/@customer.onmicrosoft.com
SharePoint – https://customer-admin.sharepoint.com
Intune – https://endpoint.microsoft.com/customer.com  
Security Center – https://securitycenter.windows.com/?tid=customer_tenant_id

Unfortunately this doesn’t seem to apply for the 365 Admin Center (please comment if you found a way to do it!), which is where you would want Helpdesk staff to be performing User / Exchange tasks, rather than jumping between Azure AD and Exchange portals. But, at least it works, and achieves the goal for security conscious customers who are hesitant to accept the partner invitation.

Here is the process:

1. Customer accepts MSP invitation

2. Customer removes the admin / helpdesk agent privileges from the partners area of the customers portal (this keeps the association and you can still procure licensing for them, but removes the default partner permissions)

3. Create two or more groups in customers Azure AD; one for your Helpdesk and one for Admins (with assign roles to group enabled)

4. Assign the roles to the Helpdesk group (change to fit your needs or use custom roles):
User Administrator
Exchange Recipient Administrator

5. Assign roles to the Admin group as required (you can use more admin groups to assign roles to different support groups if required):

Intune Administrator
Authentication Administrator
Exchange Administrator
User Administrator
Guest Inviter
Application Administrator
Compliance Administrator
Global Reader
Conditional Access Administrator
Cloud App Security Administrator
License Administrator
Azure AD Joined Device Local Administrator
Groups Administrator
SharePoint Administrator
Privileged Role Administrator
Azure Information Protection Administrator
Security Administrator

6. Assign Azure subscription roles to the Admin groups as required:
Contributor

7. You can also use these groups to assign permissions to certain Azure objects, using the IAM blade under the resource

8. Use ‘Bulk Invite’ in customers Azure AD users blade to invite all your support staff to the customer tenant as guests

9. Add the invited guest accounts to the groups you created as required

After a support staff member accepts the invitation, they can open the URLs mentioned above using their standard user account to perform tasks in a customer tenant.

Not perfect, but it does work and avoids using generic credentials to perform tasks in a customer tenant.

Exchange Online – set default Retention Policy if null

# EDIT # I have updated this script due to an issue where multiple mailboxes are matched due to similar names, this line below with $mailboxes variable piped to the Set command uses Display Name for Identity which may not be unique. Script is updated to loop through the mailboxes using UPN for the Set command. Cheers! 🍺

$mailboxes | Set-Mailbox -RetentionPolicy $defaultpolicy.Name

I’ve come across several clients lately who are migrating to or have migrated to Exchange Online, and find some users have no retention policy set. This script can be scheduled in an Azure runbook to find enabled users with no policy and set it to the default policy. Replace ‘svc-runbookcred’ with your runbook credential name. Easily modified to connect to on premise Exchange; if you need any help just add a comment below! 🙂

# use TLS 1.2
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

# specify runbook credential name
$runbookcredential = 'svc-runbookcred'

# get credential for eol connection
Try { 
    $CredAzure = Get-AutomationPSCredential -Name $runbookcredential
}
        Catch {
            Write-Error "Failed to get credential!"
            Exit
        }   
Write-Output "Get automation credential - Success"

# connect eol
Try {
    Connect-ExchangeOnline -Credential $CredAzure
}
        Catch {    
            Write-Error "Failed to connect to MSOnline!"
            Exit
        }
Write-Output "Connect to EOL - Success"

# get default policy from org settings
Try {
    $defaultpolicy = Get-RetentionPolicy | Where-Object { $_.IsDefault -eq $true }
}
        Catch {    
            Write-Error "Failed to get default policy!"
            Exit
        }
Write-Output "Get default policy - Success"

# find enabled mailboxes with no policy set
Try {
    $mailboxes = Get-Mailbox -ResultSize Unlimited -Filter { ( RecipientTypeDetails -eq 'UserMailbox' ) -and ( ExchangeUserAccountControl -ne 'AccountDisabled') } | Where-Object { $_.RetentionPolicy -eq $null }
}
        Catch {    
            Write-Error "Failed to get mailboxes!"
            Exit
        }
Write-Output "Get mailboxes - Success"

# set to default policy
Try {
    foreach ($mailbox in $mailboxes) {
        Set-Mailbox -Identity $mailbox.UserPrincipalName -RetentionPolicy $defaultpolicy.Name
    }
}
        Catch {    
            Write-Error "Failed to set policy!"
            Exit
        }
Write-Output "Set default policy - Success"

# end

Azure Runbook – enable Exchange Online Litigation Hold

This script will connect to Exchange Online and enable litigation hold for all enabled users. Errors due to not having the appropriate license are ignored. Litigation hold can be enabled for users licensed with Business Premium, EOL Plan 2 or the Mailbox Archive add-on. Replace ‘svc-runbookcred’ with your runbook credential name. You can schedule to run nightly to pick up new users as they are added. If you like the script, made it cooler or need some help, please add a comment below! 🙂

# use TLS 1.2
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

# specify runbook credential name
$runbookcredential = 'svc-runbookcred'

# get credential for eol connection
Try { 
    $CredAzure = Get-AutomationPSCredential -Name $runbookcredential

}
        Catch {
            Write-Error "Failed to get credential!"
            Exit
        }   
Write-Output "Get automation credential - Success"

# connect eol
Try {
    Connect-ExchangeOnline -Credential $CredAzure
}
        Catch {    
            Write-Error "Failed to connect to MSOnline!"
            Exit
        }
Write-Output "Connect to EOL - Success"

# get user mailboxes
Try {
    $mailboxes = Get-Mailbox -ResultSize Unlimited -Filter { ( RecipientTypeDetails -eq 'UserMailbox' ) -and ( ExchangeUserAccountControl -ne 'AccountDisabled') } | Where-Object {$_.LitigationHoldEnabled -ne $true}
}
        Catch {    
            Write-Error "Failed to get user mailboxes!"
            Exit
        }
Write-Output "Get user mailboxes - Success"

# enable litigation hold
Try {
    $mailboxes | Set-Mailbox -LitigationHoldEnabled $true -ErrorAction Ignore
}
        Catch {    
            Write-Error "Failed to enable litigation hold!"
            Exit
        }
Write-Output "Enable litigation hold - Success"

Azure Runbook – Licensing Alert

I created this script for a client that wanted to know when they had no available licenses for any SKU.  I’m sure they will add this to the portal soon (?)

The goal is simple – if my consumed no. of licenses = available licenses for any given SKU, send an email to me and my CSP so I can replenish before it becomes a problem.  Easily modified to alert at any number of remaining available licenses. e.g. to alert when there are 5 available licenses change ($_.ConsumedUnits -eq $_.ActiveUnits) to ($_.ConsumedUnits -eq $_.ActiveUnits-5).

The script is written to run as an Azure PowerShell Runbook, which allows use of a credential stored in the automation account, as well as using output to have some nice text show up in the portal logs.  I’m assuming you have set this stuff up already (if you haven’t, google it and get it sorted =). I’ll do a post soon on how to do it but it is not too difficult.

Azure blocks outbound connections on port 25, so no going there!  But aha, they do allow secure port 587.  So I use a ‘soon to be’ deprecated command called send-mailmessage to send the email using a free SendGrid account (no cost for 100 emails per month) which is plenty enough for this solution.

Disclaimer ## as I was testing the script, I noticed Azure now has a ‘SendGrid solution’ where you can sign up to SendGrid free from within the Azure portal – awesome!  Shame I missed it… if I update to using that method I will update this post =).  My understanding is that you could sign up for that, then use it by calling a ‘playbook’ from the automation script.

Here is the script (replace $smtppswd with your sendgrid API key SG.xxxxxx, replace $runbookcredentialname with your runbook cred name, replace $mailfrom and $mailto).  Also, since there may be 0 available licenses or 1000 freely available licenses, by default I’m only considering available license values >1 <500.  Change to suit your needs!

If you have any problems or made the script cooler (like sending the info in an HTML table) please add a comment below! 🙂

# use TLS 1.2
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

# check for any licenses out of stock and send a notification - Simon Burbery - August 2021

# create credential for sending email via SendGrid
$smtpuser = 'apikey'
$smtppswd = ConvertTo-SecureString -String 'SG.xxxxxxx' -AsPlainText -Force
$CredSMTP = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $SMTPuser, $SMTPpswd

# set variables
$runbookcredentialname = 'svc_runbookaccount'
$mailfrom = 'Azure License Notifcation <sendmail@place.co.nz>'
$mailto = @("<admin@place.co.nz>", "<azurealerts@place.co.nz>")
$mailsubject = 'Warning - out of licenses!'
$mailbody = 'Availability of one or more of your license SKUs has reached zero:'
$mailserver = 'smtp.sendgrid.net'
$mailport = '587'
$mailcredential = $CredSMTP

# get credential for msol connection
Try { 
    $CredAzure = Get-AutomationPSCredential -Name $runbookcredentialname
}
        Catch {
            Write-Error "Failed to get credential!"
            Exit
        }   
Write-Output "Get automation credential - Success"

# connect msol
Try {
    Connect-MsolService -Credential $CredAzure
}
        Catch {    
            Write-Error "Failed to connect to MSOnline!"
            Exit
        }
Write-Output "Connect to MSOL - Success"

# license check
$skucheck = Get-MsolAccountSku  | Where-Object { ($_.ActiveUnits -gt 0) -and ($_.ActiveUnits -lt 500) -and ($_.ConsumedUnits -eq $_.ActiveUnits) }

# email body format
$mailbodyfinal = $mailbody,$skucheck | Out-String -Width 500

# send notification
If ( $skucheck -ne $null ) {
    $MailParameters = @{
        From = $mailfrom
        To = $mailto
        Subject = $mailsubject
        Body = $mailbodyfinal
        SmtpServer = $mailserver
        Port = $mailport
        Credential = $CredSMTP
        UseSsl = $true
        }
        Send-MailMessage @MailParameters
            If  ($? -ne $true) { 
    Write-Error "Failed to send email notification!" 
    }
                Else {
                Write-Output "Send email notification - Success"
            }       
}
    Else {
        Write-Output "No licensing issues detected"
    }

# end

Azure AD – export groups and members to CSV

UPDATE June ’22 – also check out Active Directory – export groups and members (with email addresses). It’s for on-premises AD but you will easily modify it for Azure AD I’m sure! Wont you? Then why haven’t I yet you say? 🤣😃🤣

UPDATE Sept ’22 – David made me do it – well, he didn’t make me at all really but I did it anyway 🙂. Check out this new post which uses AzAD cmdlets to get the groups and members email, UPN and ObjectID: Azure AD – export groups and members #2.

# export azure ad groups and members to csv (also output empty groups with 'No Members' value) 
# assumes existing connection to Azure AD using Connect-AzureAD (or use a runbook)

$allgroups = Get-AzureADGroup | select ObjectId,DisplayName

$result = foreach ( $group in $allgroups ) {

    $hash = @{GroupName=$group.DisplayName;Member=''}
    $groupid = $group.ObjectId
    
    if ( $members = Get-AzureADGroupMember -ObjectId $groupid ) {
            
            foreach ( $member in $members ) {

                $hash.Member = $member.DisplayName
                New-Object psObject -Property $hash
            }
            }
    else
        {
        $displayname = "No Members"
        $hash.Member = $displayname
        New-Object psObject -Property $hash
        }
}

$result | Export-Csv -Path C:\temp\AzureADGroups.csv -NoTypeInformation

# End

PowerShell get azure ad group members export to csv

export azure ad group members to csv PowerShell

PowerShell export azure ad user group membership to csv

Export Active Directory groups and members to CSV file

UPDATE 2022-04-02 – if you would like email addresses with the output, check out my new post at: https://www.howdoiuseacomputer.com/index.php/2022/04/02/export-active-directory-groups-and-members-to-a-csv-file-with-email-addresses/

# export active directory groups and members to csv (also output empty groups with 'No Members' value)
# assumes run on domain controller or import of ActiveDirectory module

$allgroups = Get-ADGroup -Filter *

$result = foreach ( $group in $allgroups ) {

    $hash = @{GroupName=$group.SamAccountName;Member=''}
    $groupid = $group.distinguishedname
    
    if ( $members = Get-ADGroupMember $groupid ) {
            
            foreach ( $member in $members ) {

                $hash.Member = $member.Name
                New-Object psObject -Property $hash
            }
    }
    else {
        $displayname = "No Members"
        $hash.Member = $displayname
        New-Object psObject -Property $hash
    }
}

$result | Export-Csv -Path C:\temp\ActiveDirectoryGroupsAndMembers.csv -NoTypeInformation

# End