Due to the popularity of the initial script (cheers!) Azure AD – Export Groups and Members to CSV, and thanks to David for asking, this script will export the groups and the members with properties ObjectID, UserPrincipalName and Email Address. This one uses the AzAD cmdlets. To import them, use:
Install-Module Az -SkipPublisherCheck -Force -AllowClobber -Confirm:$false
I’m starting off-topic (it does say Ramblings at the top of the screen after all) but stay with me!
😃👀🍻👀😃
Hiding your DNS might seem like a good idea; I have nothing against as long as it is free, but to me it is like changing RDP port 3389 to 4444 (not that you have RDP directly exposed to the internet of course – you have an RD Gateway!). Sure, you’ve made it harder to identify an available target, but if someone is really after you they’ll have methods to get around this pretty quickly using port scans or other techniques. You’ve also made it harder for people trying to help to work out what is going on.
The best thing you can do is hurry up on that cloud journey so you can offload concerns about external IP addresses exposing your on-premises entry points. And as long as you are keeping those entry points secure and up to date, there shouldn’t be any major concern here anyway.
If you are running RDS or Citrix services, generally these do not work well with SSL inspection or pre-authentication so are configured to pass-through directly to the entry point i.e. Cloudflare is providing minimal benefit here.
Instead of being fancy pants and paying for these services to provide minimal benefit, let’s look at a great set up for minimal cost that let’s you easily add, remove or change DNS entries right in the portal you use everyday!
First, make sure your entry points are secure:
Ideally, make sure you have a Web Application Firewall in front of web facing services. This comes at a cost though, which some of us like to avoid if possible 😜
Whether you do or not, review the ciphers available and remove them from least secure upwards until you work out the minimal and most secure configuration. For Windows IIS, remove any ciphers you don’t need as described here: Secure your Web Server
Do you have an RD Gateway / Web server? The same applies with IIS, and make sure you don’t allow ‘Domain Users’ in the access policies. Refine this to a group that contains only the users that need access. I’m sure you can get by without ‘Administrator’ being available to brute force hack from outside.
Geo-blocking – use it! Most firewalls have some geo capability these days. If your users are in New Zealand and Australia, restricting access to those regions only at the firewall provides a huge security benefit.
Right what was I actually posting about? Ah yes – Azure DNS is easy to set up and costs literally a dollar and cents per month. You don’t transfer your records to Azure though as they are not (and may never be) a registrar. But no bother, once you’ve set up your records in Azure, you simply change the ‘nameserver’ configuration with your existing provider. I use Free Parking in New Zealand, a great low(ish) cost no-frills provider. About $45/year for a domain name and of course they have DNS management, but I’d rather do it in Azure so I after I configured the zone, I copied the four nameserver entries on the right:
…then changed my nameservers from Free Parking to Azure – there will be somewhere you can do this in your providers portal, or just log a request for them to do it:
Done! Once the update is complete you are now serving and managing your DNS entries from the familiar Azure portal.
Here is my Azure DNS zone cost… as you can see – cheap as cheeps mate! Yes I’ll pay a whopping NZD$1.58/month or $18.96/year. That’s only 5.42857 Steak ‘n’ Cheese from Mrs. Miggins pie shop! 🥧
The console is intuitive and I love the fact I can manage my DNS easily and securely from within my Azure tenant. I can also get some metrics about the DNS usage that I couldn’t get before:
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:
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
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.
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"
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 - check credential!"
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
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
recent comms…