<< A B C D E F G H I J K L M N O P Q R S T U V W X Y Z

–A–

applications (Azure), list – see Azure enterprise applications, list

array, remove members of one array from another array

I do this when I want to enable MFA to a whole domain but exclude system accounts

$usersAvengers = Get-MsolUser -All | ? {$_.userprincipalname -match "Avengers.com" -and $_.StrongAuthenticationRequirements.State -eq $null -and $_.UserType -ne "Guest" -and $_.isLicensed -eq $true} | sort UserPrincipalName
$exclude = @("CaptainAmerica@Avengers.com", "thor@Avengers.com", "IronMan@Avengers.com")
$AvengersNoSystemUsers = $usersAvengers | ? {$_.UserPrincipalName -notin $exclude}

audit log, search

let's focus on one user. What to use for the -UserIds field? It seems that the first part before the "@" sign will work.

$someGuy = Search-UnifiedAuditLog -UserIds "someGuy" -StartDate (Get-Date).AddDays(-7) -EndDate (Get-Date) -ResultSize 5000

how many records associated this user:

$someGuy.Count

count of unique operations performed by this user:

$someGuy.Operations | select -Unique

let's home in on what files he modified.

$someGuy | ? {$_.Operations -eq "FileModified"} | fl

Yuck! It looks like the data we're interested is all clumped together in a somewhat unreadable AuditData variable. Luckily, we can parse it out pretty easily.

$ConvertedOutput = $someGuy | Select-Object -ExpandProperty AuditData | ConvertFrom-Json

Now that the AuditData variable is parsed out, display some relevant fields.

$ConvertedOutput | Select-Object CreationTime, UserId, Operation, Workload, ObjectID, SiteUrl, SourceFileName, ClientIP, UserAgent | ogv

Azure, connect to – see connect to Azure

Azure enterprise applications, list

to list all Azure enterprise applications

Get-AzureADServicePrincipal -All $True | Sort-Object DisplayName | ft

Note that if you omit the -All $true parameter, you'll get fewer groups than if you don't.

Azure groups, show – see also Azure security groups, show

to list all groups

Get-AzureADGroup -All $True | Sort-Object DisplayName | ft

Note that if you omit the -All $true parameter, you'll get fewer groups than if you don't.

for just one user

$UserIdentity = "someUser"

$User = Get-ADUser -Identity $UserIdentity

all the Azure groups to which the user belongs

Get-AzureADUser -SearchString $User.UserPrincipalName | Get-AzureADUserMembership

remove the user from all Azure groups to which he belongs

Get-AzureADUser -SearchString $User.UserPrincipalName | Get-AzureADUserMembership | % {Remove-AzureADGroupMember -ObjectId $_.ObjectId -MemberId (Get-MsolUser -UserPrincipalName $User.UserPrincipalName).ObjectId}

the Get-AzureADUser command doesn't work anymore. Instead, use Get-MgUser:

Get-MgUser -ConsistencyLevel eventual -Count userCount -Search "DisplayName:Marley, Bob"

Azure security groups, show

"pure" security groups (not email enabled)

Get-AzureADGroup -All $true | ? {$_.MailEnabled -eq $false -and $_.SecurityEnabled -eq $true}

email enabled security groups

Get-AzureADGroup -All $true | ? {$_.MailEnabled -eq $true -and $_.SecurityEnabled -eq $true}

Azure version – see version of Azure

–B–

–C–

capital letters, change to proper case – see proper case

connect to Azure – see also Connect-AzureAD not recognized

$User = "someUser@yourDomain.com"
$PWord = ConvertTo-SecureString -String "topSecret" -AsPlainText -Force
$cred = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $User, $PWord
Connect-AzureAD -Credential $cred

under some circumstances,you'll instead want to use:

Connect-MSOLservice -Credential $cred

And if that has problems, check which version of MSOnline you have: you may need to update

Connect-AzureAD not recognized

Install-Module az
Import-Module az

create user – see user, create

created user, when? – see user created, when?

credentials

$User = "someUser@yourDomain.com"
$PWord = ConvertTo-SecureString -String "topSecret" -AsPlainText -Force
$cred = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $User, $PWord

–D–

delete user – see user, delete

delete orphaned synced user attempt, but system won't allow you to delete – see synced user in AAD has no corresponding user in AD and won't let you delete

deleted user (soft deleted), delete permanently – see user, soft deleted - delete permanantly - useful in a situation where you're trying to delete a user but the system claims some other, similarly named user is already clogging up the deleted users

deleted users (soft deleted), list one in particular

This will also get his immutable ID

Get-MsolUser -All -ReturnDeletedUsers | ? {$_.UserPrincipalName -eq "Bob.Marley@JamaicanMusic.org"} | Select-Object UserprincipalName,ImmutableID,LastDirSyncTime

deleted users (soft deleted), list

Get-MsolUser -All -ReturnDeletedUsers | Sort-Object UserPrincipalName | ft UserPrincipalName, DisplayName, ObjectId

deleted user (soft deleted), restore when UserPrincipalName has a domain not accepted by the tenant - see user, restore when UserPrincipalName has a domain not accepted by the tenant

deleted groups, show – see groups deleted, show

display name, change

Set-MsolUser -UserPrincipalName "LadyDianaFrances@Spencer.com" -DisplayName "Diana, Princess of Wales"

But once I got

Unable to update parameter. Parameter name: DISPLAYNAME

I found out this happens when you try to do it with a user that's synced. And, sure enough, the user was synced. But the display name was right in local AD. So, how could I possibly set it again to the right value in the cloud? I read somewhere that this command would fix:

Set-MsolDirSyncEnabled -EnableDirSync $false

And, sure enough, I could then run the Set-MsoluUser command above just fine. But then I couldn't reverse the command to set EnableDirSync to true afterwards! I kept getting the error:

You cannot turn off Active Directory synchronization

Not only that, but my sync server started complaining "stopped-server-down"! Argh, what to do? Well, it turns out when you run the command to top syncing, it puts it in a "pending" state as revealed by this command:

Get-MsolCompanyInformation | select DirectorySynchronizationStatus

which, indeed, showed:

DirectorySynchronizationStatus
------------------------------
PendingDisabled

So you just have to wait (12 hours?) for that to settle down before attempting to set back to "true". That is, wait for the result of the command above to change before finally issuing:

Set-MsolDirSyncEnabled -EnableDirSync $True

Not sure it was worth going through all that just to fix that little display problem!

distribution group, allow external users to mail to internal distribution group – see group, allow external senders

distribution group, find by wildcard – see wild card search

domain, display domain part of UserPrincipalName

Get-MsolUser -UserPrincipalName Jack@Daniel.com | Select @{n="Dom";e={$_.UserPrincipalName.split("@")[1]}}

domain, filter MsolUser by – see Get-MsolUser filter

domain, find users who belong to a particular domain

fast

Get-Msoluser -DomainName needleinhaystack.com -All

for better or worse, this brings in not only users with this particular domain in their UserPrincipalName but also any users with this domain in any of their proxyAddresses. So, if you really only want users with this domain in their UserPrincipalName, go slow.

slow

Get-MsolUser -All | where {($_.userprincipalname -match "needleinhaystack.com")}

domain, find all users - sort by domain

Get-MsolUser -All | Select-Object @{n="Dom";e={$_.UserPrincipalName.split("@")[1]}}, displayName, userprincipalname | Sort-Object dom, displayName

domain, force removal from tenant

Remove-MsolDomain -DomainName "yourdomain.com" -Force

domains, list all for a tenant

new

Get-MgDomain | sort ID | select ID, AuthenticationType

old

Get-MsolDomain | sort name | select Name

domain licensed mailboxes – see multifactor authentication (MFA), find all mailboxes for a domain, whether and how their MFA is enabled and what licenses they have

domains, list all accepted in a tenant

dupes, find users with duplicate display names – see Find IDs With Duplicate Display Name or ghost users, track down (duplicates, etc.)

Some commands seem to only work by using the DisplayName or the only other alternative might be to specify the ObjectId. This means if you try to update or add using this, system will complain that more than one user has the same DisplayName.

dynamic distribution group, create

New-DynamicDistributionGroup -Name "Employees US" -RecipientFilter "CustomAttribute5 -eq 'United States'"

dynamic distribution group email address, change

Set-DynamicDistributionGroup -Identity "Employees US" -WindowsEmailAddress "EmployeesUS@conglomerate.com"

dynamic distribution groups, list

Get-DynamicDistributionGroup

dynamic distribution group, list members

Get-DynamicDistributionGroupMember -Identity "Employees US" | ft

–E–

email-enabled security group - see also group member, add

email user info

Get-MsolUser -userprincipalname test@yourdomain.com | fl

enterprise applications (Azure), list – see Azure enterprise applications, list

extended attributes or properties – see New-AzureADApplicationExtensionProperty

external user, invite – see invitation, send

external users, allow to mail to internal distribution group – see group, allow external senders

external users - this has to do with SharePoint users - see also guest users

For an external user to be listed using this PowerShell cmdlet, they need to have accepted the invitation to the SharePoint Online environment, and have logged in at least once.

Get-SPOExternalUser | Select DisplayName,Email,AcceptedAs,WhenCreated | Format-Table

But the Get-SPOExternalUser cmdlet only returns first 50 users. So, we need to amend the script a bit to get all external users in SharePoint Online. So:

Try {
    For ($x=0;;$x+=50) {
        $ExternalUsers += Get-SPOExternalUser -PageSize 50 -Position $x -ErrorAction Stop
    }
}
Catch {}
$ExternalUsers | ft

In some cases, the Get-SPOExternalUser cmdlet doesn't return all external users. So, use Get-SPOUser cmdlet to get external users by site collection. See here for details and a script to do that.

external users belonging to groups – see groups with external users

–F–

filter

filter MsolUser on domain – see Get-MsolUser filter

–G–

Get-MgUser

Get-MgUser -ConsistencyLevel eventual -Search "DisplayName:HealthMailbox"

Get-MgUser, permissions required to run

Find-MgGraphCommand -command Get-MgUser | Select -First 1 -ExpandProperty Permissions

Get-MsolUser filter

filter on domain

Get-Msoluser -DomainName NewlyMigrated.com -All

filter on usage location

Get-Msoluser -UsageLocation UK -All

ghost users, track down (duplicates, etc.) - see also Find IDs With Duplicate Display Name

Sometimes the system claims there's interference with users you just can't seem to find anywhere. This is a list of commands to try to find locations of these interferences.

$m="someUser@yourTenant.onmicrosoft.com"
Get-MsolUser -All | where-Object {$_.ProxyAddresses -match "$m"} | fl
Get-MsolUser -All | Where-Object {$_.UserPrincipalName -match "$m"} | fl
Get-MsolContact -All | Where {$_.EmailAddress -match "$m"} | FL
Get-MsolGroup -All | Where-Object {$_.ProxyAddresses -match "$m"} | fl
Get-MsolUser -ReturnDeletedUsers -All | Where-Object {$_.ProxyAddresses -match "$m"} | fl
#Get-Recipient -ResultSize unlimited | Where {$_.EmailAddresses -match "$m"} | FL Name, RecipientType, emailAddresses
Get-Recipient -ResultSize unlimited | Where {$_.EmailAddresses -match "$m"} | FL
Get-Mailbox -SoftDeletedMailbox | Where {$_.EmailAddresses "$m"} | FL
Get-Recipient -Filter: "name -like '*cnf*'" | fl
Get-Mailbox -PublicFolder | FL EmailAddresses

global admins, list

Get-MsolRoleMember -RoleObjectId $(Get-MsolRole -RoleName "Company Administrator").ObjectId

global admin role, add

Add-MsolRoleMember -RoleName "Company Administrator" -RoleMemberType User -RoleMemberEmailAddress "gladUR@promoted.com"

global admin role, remove

Remove-MsolRoleMember -RoleName "Company Administrator" -RoleMemberType User -RoleMemberEmailAddress "sadUR@demoted.com"

group, add (create)

Azure group:

New-AzureADGroup -DisplayName "Employees US Security" -MailEnabled $false -SecurityEnabled $true -MailNickName "EmployeesUSSecurity"

Oddly, you seem to need the MailNickName even if you set -MailEnabled to $False. And you can't set -MailEnabled to $True. If you want an email-enabled security group, you need to create an email-enabled distribution group of type "security" instead:

email-enabled security group:

New-DistributionGroup -Name "Accounting NetSuite" -Alias AccountingNetSuite -Type Security

Unified or Team group:

New-UnifiedGroup -DisplayName "Accounting NetSuite2" -Name "Accounting NetSuite2"

group, allow external senders

populate variable with list of groups

$distGroups = Get-DistributionGroup -identity LetEmALLIn*

display current settings

$distGroups | select Name, DisplayName, RequireSenderAuthenticationEnabled

change settings

$distGroups | Set-DistributionGroup -RequireSenderAuthenticationEnabled $False

group, copy all members from one group to another group – see also group, move users listed in CSV from one group to another

This assumes you already know the ObjectID of your target.

Get-AzureADGroup -All $True | ? {$_.DisplayName -eq "SourceGroup"} | Get-AzureADGroupMember | % {Add-AzureADGroupMember -ObjectId "73ea0b84-006d-48f6-ba34-8a1d2bdb7cad" -RefObjectId $_.ObjectId}

where 73ea0b84-006d-48f6-ba34-8a1d2bdb7cad& is the ObjectID of your target in this example.

group deleted, delete permanently

first, get the ID

Get-AzureADMSDeletedGroup

once you have the ID, use it to permanently delete

Remove-AzureADMSDeletedDirectoryObject -Id cfcd3d3d-e488-41d1-b358-b13d8f7a9306

group, find – see also wild card search

Get-AzureADGroup -SearchString "IT Gr"

or, if that doesn't know but you know the objectID:

Get-AzureADObjectByObjectId -ObjectIds 4ac67f63-275e-49ca-9a57-f36271c533f3 | fl

dynamic group, disable message to users telling them they've joined

Get-UnifiedGroup -Identity "YouveWon@PublishersClearinghouse.onmicrosoft.com" | select WelcomeMessageEnabled
Set-UnifiedGroup -Identity "YouveWon@PublishersClearinghouse.onmicrosoft.com" -UnifiedGroupWelcomeMessageEnable:$false

group, licenses for each member

This

$tenant = (Get-MsolAccountSku)[0].AccountSkuId.split(":")[0]
$prefixLength = $tenant.Length+1
$IntuneServiceNotInstalled = Get-AzureADGroupMember -ObjectId (Get-AzureADGroup -SearchString "IntuneServiceNotInstalled").ObjectID
$licenses = $IntuneServiceNotInstalled | % {
    $MsolUser = Get-MsolUser -UserPrincipalName $_.UserPrincipalName;
    $licensesThisUser = $MsolUser.Licenses.AccountSkuId.Substring($prefixLength)
    $licensesCombinedIntoOneString = $licensesThisUser -join ', '
    $intune = $licensesThisUser -contains "INTUNE_A"
    $O365Premium = $licensesThisUser -contains "O365_BUSINESS_PREMIUM"
    New-Object -TypeName PSObject -Property @{
        "Display Name" = $_.DisplayName
        licenses = $licensesCombinedIntoOneString
        intune = $intune
        O365Premium = $O365Premium
}}
$licenses | ogv

groups, list – see Azure groups, show

group member? Is someone a member of a group?

$division = "Clean plate club"
$UPN = "cookie@monster.com"
$members = Get-DistributionGroupMember -Identity $division
$members.Count
$members | ogv
If ($members -contains $UPN) {Write-Host "'$UPN' is a member of '$division'" -ForegroundColor Green}
Else {Write-Host "'$UPN' is NOT a member of '$division'" -ForegroundColor Red}

group member, add (aimed at email-enabled security groups but should also work for distribution groups) – see also group, move users listed in CSV from one group to another

What you might think ought to work (if you search how to add users to a group) but doesn't:

normally you'd have to dig up the object IDs, which is a pain:

Add-AzureADGroupMember -RefObjectId fd51d77f-5906-4ffa-a890-40c2dac9243 -ObjectId 37e9523e-4c8e-446f-8cc5-9bcb58d59c87

The above should work. But it usually fails with “Unable to update the specified properties for objects that have originated within an external service”.

another way which will also probably not work:

Add-AzureADGroupMember -RefObjectId (Get-AzureADUser -SearchString "Frodo" | ? {$_.DisplayName -eq "Frodo Baggins"}).ObjectID -ObjectId (Get-AzureADGroup -SearchString "Fellowship" | ? {$_.DisplayName -eq "Fellowship of the Ring"}).ObjectID

There are a couple extra SearchString in there that might seem redundant. But filtering ahead of time before the "?" clause reduces the time taken for the searches. Otherwise, you'd have to put an -All:$true in right after the Get-AzureADUser to make sure you got all AzureAD records and that adds time.

This also works

Add-AzureADGroupMember -ObjectId (Get-AzureADGroup -SearchString "Fellowship of the Ring" ).ObjectID -RefObjectId (Get-MsolUser -UserPrincipalName frodo@shire.com).ObjectID

as should this for a CSV containing several users you want to add:

$Groupid = (Get-MsolGroup -SearchString "Fellowship of the Ring").ObjectID
Import-CSv -Path "FellowshipOfTheRing.csv" | % {
    $UserID = (Get-MsolUser -UserPrincipalName $_.UserPrincipalName).ObjectID
    Add-MsolGroupMember -GroupObjectId $GroupID -GroupMemberObjectId $UserID -GroupMemberType User
}

What actually works

Instead, you must use the Add-DistributionGroupMember Exchange command.

$DeweyCheetumIDs = Get-MsolUser -All | ? {$_.UserPrincipalName -like "*DeweyCheetum*"}
foreach ($user in $DeweyCheetumIDs) {
    Add-DistributionGroupMember -Identity "Employees AndHowe" -Member $user.DisplayName
}

find IDs with duplicate DisplayName

you may get complaints if you have multiple users with the same DisplayName. The only way I've found is to find the dupes. First, load all users into an array.

$AllUsers = Get-MsolUser -All | sort DisplayName
$DisplayNames = @()
$AllUsersCount = $AllUsers.count
foreach ($user in $AllUsers) {
    $i++
    $DisplayNames += "$($user.DisplayName)"
}

Note we expand our scope beyond the original where statement above by not only not restricting it by the where statement but also using the -All filter.

Find the dupes:

$ht = @{}<
$DisplayNames | foreach {$ht["$_"] += 1}<
$ht.keys | where {$ht["$_"] -gt 1} | foreach {write-host "Duplicate element found $_"}

After you find this to find the dupes, then manually delete them. And then run the same looped Add-DistributionGroupMember command above.

group members, show (for AzureADGroup)

first, make sure you have the group name right

Get-AzureADGroup | sort DisplayName

then list all the members

Get-UnifiedGroupLinks -Identity "Some Group" -LinkType Members | select name, RecipientType, RecipientTypeDetails, UserState | sort name

group members, show

distribution group

Get-DistributionGroupMember -Identity "Golden Horde"

azure group

Get-AzureADGroupMember -ObjectId (Get-AzureADGroup -SearchString "Auditor Meetings").ObjectID

security group

this will show a compressed list of one line per group with concatenated owners and members as fields in each line for all security groups with the word "marketing" in the group's DisplayName

$marketingGroups = Get-AzureADGroup -All $True -SearchString "marketing"
$result = @()
$marketingGroups | ForEach-Object {
    $group = $_
    $owners = Get-AzureADGroupOwner -ObjectId $group.ObjectId | select DisplayName
    $members = Get-AzureADGroupMember -ObjectId $group.ObjectId | select DisplayName
    $result += New-Object PSObject -property @{
        GroupName = $group.DisplayName
        members = $Members.DisplayName -join ","
        owners = $owners.DisplayName -join ","
        description = $group.Description
    }
}
$result | ogv
$result | Export-Csv -Path "$([environment]::getfolderpath("mydocuments"))\MarketingGroups$((Get-Date).ToString('MM-dd-yyyy_hh-mm-ss')).csv" -NoTypeInformation -Encoding UTF8

same as above except expanded out into separate line for each member instead of concatenated (and, of course, no owners)

foreach ($group in $marketingGroups) {
  $thisGroup = Get-AzureADGroupMember -ObjectId $group.ObjectID | sort DisplayName
  foreach ($member in $thisGroup) {
    $result += New-Object PSObject -property @{
      GroupName = $group.DisplayName
      member = $Member.DisplayName}
  }
}

group members who have been issued an invitation but have not accepted – see guest users belonging to a group, invitations not accepted

group, move users listed in CSV from one group to another

$dir = [environment]::getfolderpath("mydocuments")
$docName = "$($dir)/ListUsersToMoveFromOneGroupToAnother.csv"
$ListUsersToMoveFromOneGroupToAnother = Import-Csv $docName
$removeFromGroup = "remove from this source group"
$addToGroup = "add to this target group"
$i = 0
foreach ($person in $ListUsersToMoveFromOneGroupToAnother)
{
    $i++
    $percent = $i/$($ListUsersToMoveFromOneGroupToAnother.count)
    $percentTxt = $percent.ToString("P")
    $user = Get-MsolUser -UserPrincipalName $person.UserPrincipalName
    $userID = $user.UserPrincipalName.split("@")[0]
    $userObjectIDTxt = $user.ObjectId.ToString()
    $isContractor = (Get-DistributionGroupMember $addToGroup | ? {$_.Name -eq $userID}).Guid
    $isEmployee = (Get-DistributionGroupMember $removeFromGroup | ? {$_.Name -eq $userID}).Guid
    Write-Host "$i - $($user.FirstName) $($user.LastName) $($user.UserPrincipalName $percentTxt% complete" -ForegroundColor Blue
    if ($isContractor) {Write-Host " $userName in target group no need to do anything" -ForegroundColor Green -NoNewline}
    else {
        Write-Host " $userName NOT in target group, must add" ForegroundColor Yellow -NoNewline
        Add-DistributionGroupMember -Identity $addToGroup -Member $userObjectIDTxt -Confirm:$false
    }
    if ($isEmployee) {
        Write-Host $userName in source group, must remove" -ForegroundColor Red
        Remove-DistributionGroupMember -Identity $removeFromGroup -Member $userObjectIDTxt -Confirm:$false
    }
    else {Write-Host " $userName NOT in source group, no need to do anything" -ForegroundColor Cyan}
}

groups, show all groups to whom someone belongs

Get-AzureADUser -SearchString social@butterfly.com | Get-AzureADUserMembership | % {Get-AzureADObjectByObjectId -ObjectId $_.ObjectId | select DisplayName, ObjectType, MailEnabled, SecurityEnabled, Description} | ft -a

perhaps we want to copy & paste output - hence the "-a" so description won't get chopped

groups (Azure), show – see Azure groups, show

groups deleted, show

Get-AzureADMSDeletedGroup

groups with external users

$Groups = (Get-UnifiedGroup | ? {$_.GroupExternalMemberCount -gt 0})
If ($Groups.Count -gt 0) {
    ForEach ($G in $Groups)
   { Write-Host "Members in" $G.DisplayName
     $Members =(Get-UnifiedGroupLinks -Identity $G.Alias -LinkType Members)
     ForEach ($M in $Members)
     { If ($M.Name -Like "*#Ext#*")
       { Write-Host "External member:" $M.Name}
     }
   }
}

guest user, invite – see invitation, send

guest users – see also external users

Get-AzureADUser | ? {($_.UserType -eq 'Guest')}

Note: Get-AzureADUser seems to max out at 100 records returned! If you want more, then you have to filter. Filter conditions are a little different than normal PowerShell conditions. For example, you need to use eq without the normal preceeding dash. I think Get-MsolUser will eventually be deprecated and replaced with Get-AzureADUser. But, for now, some of the Get-MsolUser below still work.

or

Get-AzureADUser -All $True -Filter "userType eq 'Guest'"

or

Get-AzureADUser -All:$true -Filter "userType eq 'Guest'"

What if we don't know the identity?

Get-AzureADUser -SearchString "elvis"

guest users, created recently (like since yesterday)

Get-MsolUser -All | ? {($_.UserType -eq 'Guest') -and ($_.WhenCreated -ge [DateTime]::Today.AddDays(-1))} | Select-Object UserPrincipalName, DisplayName, WhenCreated

Remember: Get-AzureADUser seems to max out at 100 records returned! This means the command above might not get all the recent records!

guest users, invitations not accepted

Get-AzureADUser -All $true | ? {$_.ExtensionProperty.userState -eq "PendingAcceptance"}

But this only gets the first few records because Get-AzureADUser seems to max out at 100 records returned. But below works better:

Get-AzureADUser -Filter "UserState eq 'PendingAcceptance'" | select Department, DisplayName, Mail, RefreshTokensValidFromDateTime | Sort-Object RefreshTokensValidFromDateTime | ft

Probably because it filters up front and there's some kind of size restriction on the number of records which Get-AzureADUser can return. Here it's sorted by RefreshTokensValidFromDateTime (which I think corresponds to "time created") which is the same order that's returned in the AAD GUI. Other times I sort by Department instead:

Get-AzureADUser -Filter "UserState eq 'PendingAcceptance'" | select Department, DisplayName, Mail, RefreshTokensValidFromDateTime | Sort-Object Department, LastName, DisplayName | ft

If you sent out a bunch of invitations all within a short time and want to see which users accepted:

Get-AzureADUser -All $true | ? {$_.RefreshTokensValidFromDateTime -gt '12/26/19' -and $_.RefreshTokensValidFromDateTime -lt '12/27/19'} |select Department, DisplayName, Mail, RefreshTokensValidFromDateTime, UserState | Sort-Object Department, LastName, DisplayName | ft

guest users belonging to a group, invitations not accepted

Get-AzureADGroup -Filter "DisplayName eq 'Some Group'" | Get-AzureADGroupMember | ? {$_.ExtensionProperty.userState -eq "PendingAcceptance"} | select Department, DisplayName, Mail, RefreshTokensValidFromDateTime | Sort-Object Department, LastName, DisplayName | ft

guest users, order by department

two different ways to do this:

Get-MsolUser - might eventually be deprecated

Get-MsolUser -All | ? {($_.UserType -eq 'Guest')} | Select-Object Department, UserPrincipalName, DisplayName, FirstName, LastName, RefreshTokensValidFromDateTime | Sort-Object Department, LastName, DisplayName | ft

Get-AzureADUser - newer, I think. And probably faster since we can filter up front instead of getting all records and then filtering.

Get-AzureADUser -Filter "userType eq 'Guest'" | select Department, DisplayName, UserPrincipalName, Mail, RefreshTokensValidFromDateTime, CreationType | Sort-Object Department, LastName, DisplayName | ft

Remember: Get-AzureADUser seems to max out at 100 records returned. This means the command above might not get all records for all departments. So maybe this might work better:

$guestUsers = Get-AzureADUser -Filter "userType eq 'Guest' and (Department eq 'Your Department' or Department eq 'Another Department')" | Select-Object Department, DisplayName, UserPrincipalName, Mail, RefreshTokensValidFromDateTime, CreationType, UserType
$guestUsers.Count
$guestUsers | ogv

GUID for a user

Get-Mailbox -identity someUser | select DisplayName, GUID, ExchangeGUID

–H–

history for user activity – see audit log, search

–I–

immutable ID

clear (that is, to delete it so the ID will no longer sync)

First, either delete the ID in local AD or move it to an OU that you don't have set up to sync. Then run a sync on your sync server. This will put your cloud version of this ID into a "soft-deleted" state.

Then you'll want to eventually run a command further below to delete his ImmutableID. But if you run it right away without first

you'll get:

Set-MsoLUser : Unable to update parameter. Parameter name: IMMUTABLEID.
At line:1 char:1
+ Set-MsoLUser -UserPrincipalName freeFromTether@CloudCompany.com -Immutabl ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : OperationStopped: (:) [Set-MsolUser], MicrosoftOnlineException
+ FullyQualifiedErrorId : Microsoft.Online.Administration.Automation.PropertyNotSettableException,Microsoft.Online.Administration.Automation.SetUser

And you'll get this error even if you do stop syncing and try to run that command to clear out the ImmutableID if you haven't already first deleted it first on AD or moved it to an OU that doesn't sync.

Anyway, after you

Run the following command on your sync server to stop syncing:

Set-MsolDirSyncEnabled -EnableDirSync $false

Restore the ID that you put into the "soft-deleted" state on the cloud:

Restore-MsolUser -UserPrincipalName freeFromTether@CloudCompany.com

Run the command to remove the user's ImmutableID

Set-MSOLUser -UserPrincipalName freeFromTether@CloudCompany.com -ImmutableID "$null"

Note: you need the quotes around null above or it won't clear properly

Go back to your sync server re-instate syncing:

Set-MsolDirSyncEnabled -EnableDirSync $true

Another reason to turn off syncing on your sync server before you try to delete on AAD directly without first deleting from AD is that you may get stopped-server-down errors on the sync server when it runs its regularly scheduled sync. But you can only set to false & then back to true about once a day (maybe 12 hours?). If you try to do again too soon, you get:

Set-MsolDirSyncEnabled : You cannot turn off Active Directory synchronization.
At line:1 char:1
+ Set-MsolDirSyncEnabled -EnableDirSync $false
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : OperationStopped: (:) [Set-MsolDirSyncEnabled], MicrosoftOnlineException
+ FullyQualifiedErrorId : Microsoft.Online.Administration.Automation.DirSyncStatusChangeNotAllowedException,Microsoft.Online.Administration.Automation.SetDirSyncEnabled

find the Immutable ID for a user:

Get-MsolUser -UserPrincipalName someUser@yourDomain.com | Select-Object UserprincipalName,ImmutableID,LastDirSyncTime

set (if you don't know the value for the $immutableID variable below, probably best to see sync local user to cloud user)

Set-MSOLuser -UserPrincipalName someUser@yourdomain.com -ImmutableID $immutableID

sync local user to cloud user – see sync local user to cloud user

invitation, send

$DisplayName = "Princess Di"
$email = "diana@princess.com"
$messageInfo = New-Object Microsoft.Open.MSGraph.Model.InvitedUserMessageInfo
$messageInfo.customizedMessageBody = "Please accept this invitation to be able to access https://royalfamily.sharepoint.com/BuckinghamPalace/SitePages/Home.aspx"
New-AzureADMSInvitation -InvitedUserDisplayName $DisplayName -InvitedUserEmailAddress $email -SendInvitationMessage $true -InviteRedirectUrl "https://royalfamily.sharepoint.com/BuckinghamPalace/" -InvitedUserType guest -InvitedUserMessageInfo $messageInfo

invitations to be a guest user not accepted – see guest users, invitations not accepted

invitations to be a guest user not accepted by members belonging to a group, find which – see guest users belonging to a group, invitations not accepted

–J–

–K–

–L–

licenses available on this tenant

Get-MsolAccountSku

which returns the same thing as those licenes ostensibly used

Get-MsolAccountSku | Where {$_.ConsumedUnits -ge 1}

license, add to users

don't assume no one on your list of users won't already have the license because if they do have the license and you try to add it, failure.

$needLicenses = @("Bambi@disney.com", "SnowWhite@disney.com", "Pinocio@disney.com")
$licenseWithoutTenantName = "INTUNE_A"
$tenantName = (Get-MsolAccountSku)[0].AccountSkuID.split(":")[0]
$licenseWithTenantName = "$($tenantName):$licenseWithoutTenantName"
foreach ($needsLicense in $needLicenses) {
    $user = Get-MsolUser -UserPrincipalName $needsLicense #.UserPrincipalName
    if ($user.Licenses.ServiceStatus.ServicePlan.ServiceName -match $licenseWithoutTenantName) {
        Write-Host"'$($needsLicense.DisplayName)' already has '$licenseWithoutTenantName'" -ForegroundColor Green
    }
    else {
        Write-Host "'$needsLicense' needs '$licenseWithoutTenantName'" -ForegroundColor Red
        Set-MsolUserLicense -UserPrincipalName $needsLicense -AddLicenses $licenseWithTenantName -ErrorVariable $ErrHandle
    }
}

replace one license for another for each person in a group. In this case, replace "Microsoft 365 Business Standard" with "Office 365 E3" for everyone in the "Executive Team". Instead of a raw array, we now have an object from which we must peel off the Display Name and UserPrincipalName

$needLicenses = Get-AzureADGroupMember -ObjectId(Get-AzureADGroup -SearchString "Executive Team")[0].ObjectID | select UserPrincipalName
$tenantName = (Get-MsolAccountSku)[0].AccountSkuID.split(":")[0]
$licenseToAddWithoutTenantName = "ENTERPRISEPACK"
$licenseToAddWithTenantName = "$($tenantName):$licenseToAddWithoutTenantName"
$licenseToRemoveWithoutTenantName ="O365_BUSINESS_PREMIUM"
$licenseToRemoveWithTenantName = "$($tenantName):$licenseToRemoveWithoutTenantName"
foreach ($needsLicense in $needLicenses) {
    $user = Get-MsolUser -UserPrincipalName $needsLicense.UserPrincipalName
    if ($user.Licenses.AccountSkuID -match $licenseToAddWithoutTenantName) {
        Write-Host "'$($user.DisplayName)' already has '$licenseToAddWithoutTenantName'" -ForegroundColor Green
    }
    else &{
        Write-Host "'$($user.DisplayName)' needs '$licenseToAddWithoutTenantName'" -ForegroundColor Red
        Set-MsolUserLicense -UserPrincipalName $needsLicense.UserPrincipalName -RemoveLicenses $licenseToRemoveWithTenantName-AddLicenses $licenseToAddWithTenantName -ErrorVariable $ErrHandle
    }
}

licensed users for a domain – see multifactor authentication (MFA), find all mailboxes for a domain, whether and how their MFA is enabled and what licenses they have

strictly speaking, this isn't quite getting "licensed users for a domain". But still useful.

licenses for a user (see also licenses, remove all for a particular user)

short version

Get-MsolUser -UserPrincipalName user@yourdomain.com | Select-Object Licenses

longer version that lists all the doo-dads associated with a license

Get-MsolUser -UserPrincipalName user@yourdomain.com | Select-Object -ExpandProperty Licenses | Select-Object -ExpandProperty ServiceStatus

licenses for each user of a group – see group, licenses for each member

licenses, how many left?

$tenantName = (Get-MsolAccountSku)[0].AccountSkuID.split(":")[0]
$licenseName = "O365_BUSINESS_PREMIUM"
$license = "$($tenantName):$license"
$Account = Get-MsolAccountSku | Where-Object {$_.AccountSkuId -eq $license}
$Result = $Account.ActiveUnits - $Account.ConsumedUnits

If you don't know the license name, then list the licenses available on this tenant to give you a clue

licenses, remove all for a domain

In particular, the code below is intended to find and remove licenses for users remaining in an old tenant after you've migrated them to a new tenant. (This assumes you weren't prescient enough to first assign all these users in your old tenant a "dummy" domain while you still had them all identified in an array in memory. Or that you tried that once but other users still left in the old domain got all confused because users they recognized now had that strange "dummy" domain.)

  1. capture list of all users recently migrated to a new domain in that new domain
  2. convert that list from an object to plain-text suitable to print out as the feedstock to create a new array in the old tenant
  3. read in that array in the old domain
  4. use this array to remove licenses from the old tenant and optionally delete the users

On the new tenant

First, capture an object containing all the users you've migrated in the new tenant. Because you had to transfer the domain before you migrated, that probably means the old users on the old tenant have a nodescript @yourOldTenant.onmicrosoft.com email suffix which makes them hard to distinguish from any other emails with the same @yourOldTenant.onmicrosoft.com email suffix. This, indeed, is the whole point of this exercise; if the users on the old tenant are easily identifiable some other way (like if you reassigned them all a "dummy" domain while you had them all identified in an array in memory), then we don't have to go through all this rigamarole!

$DomainUsers = Get-Msoluser -DomainName NewlyMigrated.com -All

Convert the object into an array

$DomainUsersArrayNewTenant = $DomainUsers | % {"$($_.UserPrincipalName)"}

Convert the array into a string to print out suitable to import into a new array in the old tenant

$DomainUsersArrayNewTenant = $DomainUsersArrayNewTenat -join '", "'

Print out the array (which has been converted into a string so we can read it into an array in the old tenant) and copy into the clipboard. You may want to capture this string and substitute the old tenant's "@oldTenant.onmicrosoft.com" for the new tenant's "@NewlyMigrated.com" in a text editor before pasting it in for the next step. Or write 1 line of code to make the substitution for you.

$DomainUsersArrayOldTenant

On the old tenant

Read in the array; you'll simply paste in what you printed out in the prior step inside the parentheses

$DomainUsersArrayOldTenant = ("dewey@globalidnet.onmicrosoft.com", "cheetum@globalidnet.onmicrosoft.com", "andHowe@globalidnet.onmicrosoft.com")

Execute code against the array to clean up the old tenant

$DomainUsersArrayOldTenant | % {
    # does the user even exist?
    if (Get-MsolUser -UserPrincipalName $_ -ErrorAction SilentlyContinue) { # if user exists, find license info
        $user = Get-MsolUser -UserPrincipalName $_
        $licensed = $False
        # remove all licenses
        For ($i=0; $i -le ($user.Licenses | Measure).Count; $i++) {
            If ([string]::IsNullOrEmpty($user.Licenses[$i].AccountSkuId) -ne $True) {
                $licensed = $true}
            }

             If($licensed -eq $true) {
                Write-Host "$($user.UserPrincipalName) is licensed ($(($user.Licenses | Measure).Count))" -ForegroundColor Green
                $licenses = Get-MsolUser -UserPrincipalName $user.UserPrincipalName | Select-Object -ExpandProperty Licenses
                # show all licenses for this user prior to deleting
                # $licenses.AccountSkuId # short 'n' sweet version
                $licenses | Select-Object -ExpandProperty ServiceStatus
                # delete all licenses for this user
                ($licenses).AccountSkuId | % {Set-MsolUserLicense -UserPrincipalName $user.UserPrincipalName -RemoveLicenses $_}
            else {Write-Host "$($user.UserPrincipalName) ($($_)) not licensed" -ForegroundColor Magenta}
            # remove user if you want
            Remove-MsolUser -UserPrincipalName $user.UserPrincipalName -Force
    } else {Write-Host "$($_) does not exist" -ForegroundColor Cyan}
}

Or, if you were prescient enough to first assign all these users in your old tenant a "dummy" domain, easier:

$BAGUsersUKAfterRename = Get-MsolUser -All | ? {$_.UserPrincipalName -match "oldDomain.eu"
foreach $user in $BAGUsersUKAfterRename) {
    $licensed $False
    #remove all licenses
    For ($i=0; $i -le ($user.Licenses | Measure).Count; $i++) {
        If ([string]::IsNullOrEmpty($user.Licenses[$i].AccountSkuId) -ne $True) {
            $licensed= $true}
        }
    If($licensed -eq $true) {
        Write-Host "$($user.UserPrincipalName) is licensed ($(($user.Licenses | Measure).Count))" -ForegroundColor Green
        $licenses = $user | Select-Object -ExpandProperty Licenses
        # show all licenses for this user prior to deleting
        #$licenses.AccountSkuId # short 'n' sweet version
        $licenses | Select-Object -ExpandProperty ServiceStatus
        # delete all licenses for this user
        ($licenses).AccountSkuId | % {Set-MsolUserLicense -UserPrincipalName $user.UserPrincipalName -RemoveLicenses $_}
    }
    else {Write-Host "$($user.UserPrincipalName) ($($_)) not licensed" -ForegroundColor Magenta}
}

licenses, remove all for a particular user

$licenses = Get-MsolUser -UserPrincipalName "someUser@yourDomain.com" | Select-Object -ExpandProperty Licenses
# show all licenses for this user prior to deleting
$licenses | Select-Object -ExpandProperty ServiceStatus
# delete all licenses for this user
($licenses).AccountSkuId | % {Set-MsolUserLicense -UserPrincipalName $DepartingUser.UserPrincipalName -RemoveLicenses $_}

licenses, who has a particular

In this case, Office Premium

Get-MsolUser -All | where {$_.isLicensed -eq "TRUE" -and $_.Licenses.AccountSKUID -eq "yourtenant:O365_BUSINESS_PREMIUM"} | select DisplayName,UserPrincipalName,isLicensed

What if we want to know who with an email ending in a particular domain does not have a particular license?

Get-Mailbox *yourDomain.com -RecipientTypeDetails UserMailbox | Get-MsolUser | ? { $_.isLicensed -eq "TRUE" -and $_.Licenses.AccountSKUID -notcontains "yourTenant:O365_BUSINESS_PREMIUM"}

licenses, who has what on this tenant?

$Sku = @{
    "EMS" = "Enterprise Mobility Suite"
    "EXCHANGEDESKLESS" = "Exch Kisok" #"Exchange Online Kiosk"
    "EXCHANGESTANDARD" = "O356 Exch Only" #"Office 365 Exchange Online Only"
    "O365_BUSINESS_PREMIUM" = "O365 Prem" #"Office Business Premium"
    "OFFICESUBSCRIPTION" = "O365 Pro+" # "Office 365 ProPlus"
    "POWER_BI_STANDARD" = "Power-BI standard"
    "SHAREPOINTENTERPRISE" = "SP Pl2" #"SharePoint Online (Plan 2)"
    "SHAREPOINTSTANDARD_YAMMER" = "SP Pl1 Ymr" # "SharePoint Online (Plan 1) with Yammer"
    "VISIOCLIENT" = "Visio" #"Visio Pro Online"
}
$logfile = "Office_365_License" + [DateTime]::Now.ToString("yyyy-MM-dd_HH-mm-ss") + ".csv"
$mytemp = [environment]::getfolderpath("mydocuments")
$logfile = $mytemp + "\" + $logfile  # your local "My Documents"
$licenseType = Get-MsolAccountSku | Where {$_.ConsumedUnits -ge 1} # list  all licenses in the tenant
$headerString = "Display Name, Domain, UPN" # Build the Header for the CSV file
$numLicenses = 0
write-host "Getting the licenses and writing the header..."
foreach ($license in $licenseType) # Loop through all license types found in the tenant to add licenseTypes
{
    $headerString = $headerString + "," + $Sku.Item($license.SkuPartNumber)
    $numLicenses++
}
$headerString = $headerString + ",Errors, ImmutableId, BlockCredential" # Add other attributres
Out-File -FilePath $LogFile -InputObject $headerString -Encoding UTF8 -append
write-host "Getting all users in the Office 365 tenant..." # Get a list of all the users in the tenant
$users = Get-MsolUser -all | where { $_.isLicensed -eq "True"}
foreach ($user in $users) # Loop through all users found in the tenant
{
    $lineString = $user.displayname -Replace",","" # use last name, comma first name as display name so remove the comma
    write-host ("Processing " + $lineString)
    $lineString = $lineString + "," + $user.UserPrincipalName.Split("@")[1]  + "," + $user.userprincipalname  #+ "," + $user.isLicensed
        for($j=0;$j -lt $numLicenses; ++$j) # Loop through all license types found in the tenant
        {
            $userhaslicense ""
            foreach ($row in $user.licenses) # Loop through all licenses assigned to this user
            {
                if ($row.AccountSkuId.ToString() -eq $licenseType.AccountSkuId[$j])
                {
                    $userhaslicense = "x"
                }
            }
            $lineString = $lineString + "," + $userhaslicense
        }
    $lineString = $lineString + "," + $user.Errors + "," + $user.ImmutableId + "," + $user.BlockCredential
    Out-File -FilePath $LogFile -InputObject $lineString -Encoding UTF8 -append
color:#D4D4D4'> -Encoding UTF8 -append

}
write-host ("Script Completed. Results available in " + $LogFile)

locked out

to unlock

Set-AzureADUser -ObjectID someUser@yourDomain.com -AccountEnabled $true

login time, last

this only works if user has a mailbox

(Get-MailboxStatistics -Identity rip@vanWinkle.com).LastLogonTime

log onto Azure – see connect to Azure

logs, search – see audit log, search

lower case, change to proper case – see proper case

–M–

manager, get

Get-AzureADUserManager -ObjectId (Get-MsolUser -UserPrincipalName BobCratchit@scrooge.com).ObjectId

manager, remove

Remove-AzureADUserManager -ObjectId (Get-MsolUser -UserPrincipalName BobCratchit@scrooge.com).ObjectId

manager, set

Set-AzureADUserManager -ObjectId (Get-MsolUser -UserPrincipalName BobCratchit@scrooge.com).ObjectId -RefObjectId (Get-MsolUser -UserPrincipalName Ebenezer@scrooge.com).ObjectId

member of groups to which someone belongs – see groups, show all groups to whom someone belongs

members of a group – see group members, show

module, version of MSOnline

(Get-Item C:\Windows\System32\WindowsPowerShell\v1.0\Modules\MSOnline\Microsoft.Online.Administration.Automation.PSModule.dll).VersionInfo.FileVersion

this can be important when using Connect-MSOLservice

multifactor authentication (MFA), enable for one user.

The code below not only enables MFA but also sets his default method. In the case below we set his default to merely sending verification requests to the phone authenticator app.

put the user's UserPrincipalName into a variable

$UserPrincipalName = "maxwell.smart@control.com"

What is the user's current status before we change? Note: if we're using Conditional Access policies, those supersede whatever's in here so we don't care. That is, whatever's set here at the individual level doesn't matter.

(Get-MsolUser -UserPrincipalName $UserPrincipalName).StrongAuthenticationMethods
(Get-MsolUser -UserPrincipalName $UserPrincipalName).StrongAuthenticationRequirements.State

queue up whether or not the user is to be enabled or enforced or whatever

$st = New-Object -TypeName Microsoft.Online.Administration.StrongAuthenticationRequirement
$st.RelyingParty = "*"
$st.State = "Enforced"
$sta = @($st)

To begin with and by default, users don't have anything in their StrongAuthenticationMethods array. Queue up the stuff we intend to change for the methods into an array. In this instance, we want requests sent to user's phone authenticator app so he only need approve to be the default. That is, we set that

We set it this way to make it easiest for him (so he doesn't have to key in those 6 digits).

$SMS = New-Object -TypeName Microsoft.Online.Administration.StrongAuthenticationMethod
    $SMS.IsDefault = $False
    $SMS.MethodType = "OneWaySMS" # user must enter 6-digit code sent to his phone via regular SMS
$Phone = New-Object -TypeName Microsoft.Online.Administration.StrongAuthenticationMethod
    $Phone.IsDefault = $False
    $Phone.MethodType = "TwoWayVoiceMobile" # user must enter 6-digit code from automated call to his phone
$App = New-Object -TypeName Microsoft.Online.Administration.StrongAuthenticationMethod
    $App.IsDefault = $False
    $App.MethodType = "PhoneAppOTP" # user must enter 6-digit code from phone app
$PhoneAppNotification = New-Object -TypeName Microsoft.Online.Administration.StrongAuthenticationMethod
    $PhoneAppNotification.IsDefault = $True
    $PhoneAppNotification.MethodType = "PhoneAppNotification" # user must approve request sent to phone app on his phone
$PrePopulate = @($App, $Phone, $SMS, $PhoneAppNotification) # not sure order of the array is important.  Clearing & resetting order doesn't appear to have any effect on the order as it's displayed using Get-MsolUser.
#$PrePopulate = @() # clear out array that gets stored for user

apply changes

Set-MsolUser -UserPrincipalName $UserPrincipalName -StrongAuthenticationRequirements $sta -StrongAuthenticationMethods $PrePopulate

the StrongAuthenticationRequirements and StrongAuthenticationMethods properties can be set independently of each other. But in this case above we set them both at the same time.

multifactor authentication (MFA), enable for all mailboxes for a domain, excluding service accounts.

Assuming you've already put all your system users into an array, start by array, remove members of one array from another array to get the domain users who aren't system users. Then

foreach ($user in $AvengersNoSystemUsers) {
     $st = New-Object -TypeName Microsoft.Online.Administration.StrongAuthenticationRequirement
     $st.RelyingParty = "*"
     $st.State = "Enabled"
     $sta = @($st)
     Set-MsolUser -UserPrincipalName $user.UserPrincipalName -StrongAuthenticationRequirements $sta}

multifactor authentication (MFA), find all mailboxes for a domain, whether and how their MFA is enabled and what licenses they have.

the first 2 lines help get rid of the extraneous tenant name when listing licenses. We limit returned results to only a single domain (DraggingOurFeetToEnableMFA.com).

We omit shared mailboxes (because they aren't actively involved with loggin on or receiving/sending emails) and Room mailbox resources (including them will cause errors because they have no users associated with them)

This adds time to execute because we must execute a Get-MsolUser command inside the loop.

$tenant = (Get-MsolAccountSku)[0].AccountSkuId.split(":")[0]
$prefixLength = $tenant.Length+1
$mailboxes = @(Get-Mailbox -ResultSize unlimited *DraggingOurFeetToEnableMFA.com | ? {$_.IsShared -eq $false -and $_.RecipientTypeDetails -ne 'RoomMailbox'}) | % {
     $MsolUser = Get-MsolUser -UserPrincipalName $_.UserPrincipalName;
     New-Object -TypeName PSObject -Property @{
     "Display Name" = $_.DisplayName
     "Domain" = $_.UserPrincipalName.split("@")[1]
     "Primary Smtp Address" = $_.PrimarySmtpAddress
     "Name" = $_.Name
     "Alias" = $_.Alias
     "MFA" = $MsolUser.StrongAuthenticationRequirements.State
     "licenses" = $MsolUser.Licenses.AccountSkuId.Substring($prefixLength) -join ', '}}
$mailboxes | ogv

You can also add a field to display whether or not these users have PowerShell enabled. This adds time to execute because a second time-consuming command (Get-User) is added inside the loop. Unlike the code above, the code below doesn't limit results to just one domain.

$tenant = (Get-MsolAccountSku)[0].AccountSkuId.split(":")[0]
$prefixLength = $tenant.Length+1
$mailboxes = @(Get-Mailbox -ResultSize unlimited | ? {$_.IsShared -eq $false -and $_.RecipientTypeDetails -ne 'RoomMailbox'}) | % {
     $MsolUser = Get-MsolUser -UserPrincipalName $_.UserPrincipalName;
     New-Object -TypeName PSObject -Property @{
     "Display Name" = $_.DisplayName
     "Domain" = $_.UserPrincipalName.split("@")[1]
     "MFA" = $MsolUser.StrongAuthenticationRequirements.State
     "RemotePowerShellEnabled" = (Get-User $_.UserPrincipalName).RemotePowerShellEnabled
     "Primary Smtp Address" = $_.PrimarySmtpAddress
     "Name" = $_.Name
     "Alias" = $_.Alias
     "licenses" = $MsolUser.Licenses.AccountSkuId.Substring($prefixLength) -join ', '}}
$mailboxes | ogv

useful to group users by their MFA status

$mailboxes | Group-Object -Property MFA -NoElement

not so useful if folks have different combinations of licenses

$mailboxes | Group-Object -Property licenses -NoElement | Sort-Object -Property Count -Descending | ogv

multifactor authentication (MFA), who doesn't have enabled? – see also multifactor authentication (MFA), find all mailboxes for a domain, whether and how their MFA is enabled and what licenses they have for a more detailed account

Only care about licensed users who aren't guests. Also good to have the email domain.

Get-MsolUser -All | ? {$_.UserType -ne "Guest" -and $_.isLicensed -eq $true} | ? {$_.StrongAuthenticationRequirements.State -eq $null} | select DisplayName, UserPrincipalName, @{N="Domain"; E={$_.UserPrincipalName.split("@")[1]}} | ogv

–N–

New-AzureADApplicationExtensionProperty

if you haven't already, first step before you can create extension properties is to have an "application" to hang them off of. It doesn't have to be a "real" application. It can be a "dummy" application. So run New-AzureADApplication below to create a new dummy application. The DisplayName name can be anything. But the IdentifierUris must be a domain accepted in your tenant. You might want to record the value of the $MyApp variable because after you leave this session and then maybe want to come back some later date to add more ExtensionProperty, this app's objectID will come in real handy.

$MyApp = (New-AzureADApplication -DisplayName "Enchanted Kingdom Properties" -IdentifierUris "https://EnchantedKingdom.com").ObjectId

As an aside, either before or right after you run the command above, you might want to run this command to see all the other apps and how the one you just created above (or plan to create real soon) might fit in:

Get-AzureADApplication | select IdentifierUris, DisplayName, ObjectID, AppID | sort DisplayName | ogv

If you're like me, you probably never even knew most of these existed. But it seems a lot of 3rd-party apps get registered here.

This command (or some variation) can also come in handy if you don't want to bother recording the value of the $MyApp variable above but you need to create a new extension property days or weeks later.

Anyway, now we're ready to create a new New-AzureADServicePrincipal . Not sure we'll ever really need this again. But we need to run this step before we finally can create what we're after: new extension properties

New-AzureADServicePrincipal -AppId (Get-AzureADApplication -SearchString "FoodChainID Properties").AppId

OK, that's out of the way. Mow we're finally ready to create a new New-AzureADServicePrincipal . Not sure we'll ever really need this again. But we need to run this step before we finally can create what we're after: new extension properties

New-AzureADApplicationExtensionProperty -ObjectId $MyApp -Name "UserType" -DataType "String" -TargetObjects "User"

Now we're finally ready to assign a value to a user for this property

$SnowWhite = Get-MsolUser -UserPrincipalName SnowWhite@EnchantedKingdom.com
Set-AzureADUserExtension -ObjectId $SnowWhite.ObjectId -ExtensionName "extension_546ae66f87c839087f943fe980b41f99_UserType" -ExtensionValue "employee"

–O–

object

Get-AzureADObjectByObjectId -ObjectIds 4ac67f63-275e-49ca-9a57-f36271c533f3 | fl

organizational relationships between tenants, list

Get-OrganizationRelationship | select Name, Enabled, OrganizationalUnitRoot, FreeBusyAccessEnabled, FreeBusyAccessLevel, WhenCreated, WhenChanged, @{n="Dom";e={$_.DomainNames -join ", "}} | Export-Csv -Path "$([environment]::getfolderpath("mydocuments"))\OrganizationRelationship $((Get-Date).ToString('MM-dd-yyyy_hh-mm-ss')).csv" -NoTypeInformation -Encoding UTF8

orphaned synced user, can't delete – see synced user in AAD has no corresponding user in AD and won't let you delete

–P–

password change

Set-MsolUserPassword -UserPrincipalName "someEmail_gmail.com#EXT#@yourTenant.onmicrosoft.com" -NewPassword "topSecret"

in bulk - also forces users to change their password on first login

Import-Csv c:\user-boxes.csv | %{Set-MsolUserPassword -userPrincipalName $_.UserPrincipalName -NewPassword "Welcome" -ForceChangePassword $true}

password expires, set to never

first verify status

Get-MsolUser -UserPrincipalName gollum@MistyMtn.com | Select PasswordNeverExpires

then set

Set-MsolUser -UserPrincipalName "gollum@MistyMtn.com" -PasswordNeverExpires $True

password expiring soon, whose?

In an environment where most users' IDs are maintained in local Active Directory, our local group policy determines whether and when users passwords expire. But what about cloud-only IDs? There are cloud-only policies for those users as well. Perhaps all of our domains' password policies are set for cloud users' passwords to expire after one year. So capture that in a variable in order to figure out how far away actual users' expiration is from that figure in a subsequent command:

$PasswordPolicy = Get-MsolPasswordPolicy -DomainName yourDomain.com

Find out

Get-MsolUser -All |Where-Object {(($_.licenses).AccountSkuId -match "EXCHANGEDESKLESS") -and ($_.immutableid -eq $null)} | select UserPrincipalName, LastPasswordChangeTimestamp, @{n="whenExpires";e={$_.LastPasswordChangeTimestamp.AddDays($PasswordPolicy.ValidityPeriod)}}

or

$domain = Get-MSOLDomain | where {$_.IsDefault -eq $true}
$validityPeriod = (Get-MsolPasswordPolicy -DomainName $domain.Name).ValidityPeriod # 730
$expireindays = 30
if($null -eq $validityPeriod){$validityPeriod = New-TimeSpan -Days 90}
$maxPasswordAge = $validityPeriod.ToString()
$users = Get-MSOLuser -EnabledFilter EnabledOnly -All | ? {"Guest" -ne $_.UserType -and $true -ne $_.PasswordNeverExpires -and (New-TimeSpan -Start (Get-Date) -End ($_.LastPasswordChangeTimestamp + $maxPasswordAge)).Days -le ($expireindays)} | Select DisplayName, LastPasswordChangeTimestamp, @{Name="passwordExpires";Expression={$_.LastPasswordChangeTimestamp + $maxPasswordAge}}, @{n="DaysTilExpire";e={(New-TimeSpan -Start (Get-Date) -End ($_.LastPasswordChangeTimestamp + $maxPasswordAge)).Days}}, PasswordNeverExpires, @{Name="PrimaryEmailAddress";Expression={($_.ProxyAddresses | ?{$_ -cmatch '^SMTP\:.*'}).split(":")[1]}}, UserPrincipalName, @{N="Licenses"; E={($_.Licenses.AccountSkuID | % {$_.split(":")[1]}) -join ", "}}
$users | ogv

password, force change on next login - change setting

default is true, below changes to false so he's not forced to change when he logs in next

Set-MsolUserPassword -UserPrincipalName "gollum@MistyMtn.com" -ForceChangePassword $false -NewPassword "myPrecious"

PowerShell command execution (remotely) - can a group of mail users execute PowerShell commands remotely? Select the -RemotePowerShellEnabled attribute

select the group you want to target

$ugroup = Get-User -ResultSize Unlimited -Filter "(RecipientType -eq 'UserMailbox') -and (UserPrincipalName -like '*yourDomain.com')"

to disable these users from being able to execute PowerShell commands:

$ugroup | % {Set-User -identity $_.UserPrincipalName -RemotePowerShellEnabled $false}

proper case

proper case, change all users' display name from all caps to proper case

$TextInfo = (Get-Culture).TextInfo
foreach ($user in $users) {
    $fixedDisplayName = $TextInfo.ToTitleCase(($user.DisplayName).ToLower()) # must first set to lower because ToTitleCase doesn't touch all caps
    "will change $($user.DisplayName) to $fixedDisplayName for $($user.UserPrincipalName)"
    Set-MsolUser -UserPrincipalName $user.UserPrincipalName -DisplayName $fixedDisplayName
}

properties - all properties for a user

Get-MsolUser -UserPrincipalName 'someUser@yourDomain.com' | Select-Object *

–Q–

–R–

recycle bin, remove user from

Remove-MsolUser -UserPrincipalName user@yourdomain.com -RemoveFromRecycleBin

recycle bin, restore user from the domain is no longer accepted by the tenant

Restore-MsolUser -UserPrincipalName someUser@baddomain.com -AutoReconcileProxyConflicts -NewUserPrincipalName someUser@yourTenant.onmicrosoft.com

rename user

rename just one user:

Set-User -Identity someUser@yourDomain.com -Name "Some User"

rename several users that satisfy some criteria:

Get-User -RecipientTypeDetails UserMailbox | ? {$_.whenCreated -gt "6/19/19 9:53 am" -and$_.whenCreated -lt "6/19/19 10:15 am"} | %{Set-User -Identity $_.UserPrincipalName -Name $_.DisplayName}

Note: when you change the name property, ID and Identity properties also seem to get reset

replicate domain controllers – see domain controllers, replicate

role, assign

New-ManagementRoleAssignment -Role ApplicationImpersonation -User 'someUser@yourDomain.com'

–S–

security groups, list – see Azure security groups, show

send guest user invitation – see invitation, send

session, kill existing

Get-PSSession | Remove-PSSession

This doesn't do diddly squat to get rid of O365 session. When I run Get-MsolUser, I get results - even after I run the command above and run Get-PSSession by itself (which returns no results).

soft deleted user, delete permanantly – see user, soft deleted - delete permanantly - useful in a situation where you're trying to delete a user but the system claims some other, similarly named user is already clogging up the deleted users

sync local user to cloud user - see also synced user, stop syncing

$guid= (get-Aduser someUser).ObjectGuid
$immutableID = [System.Convert]::ToBase64String($guid.tobytearray())
Set-MSOLuser -UserPrincipalName someUser@yourdomain.com -ImmutableID $immutableID

This alone, all by itself, doesn't work so good if you have one user synced that you don't care about but whose immutable ID you want to steal to apply to a cloud-only user which actually has useful stuff you care about in it that you want to attach to a local AD user. Let's say you have two similarly-named users:

Get-MSOLuser -SearchString "someUser" | Select-Object UserPrincipalName, ImmutableID

Assume the command returns two results: one synced & one cloud-only. And you want to sync the cloud-only ID with your local ID. So you attempt to set your cloud-only ID's immutable ID to that of your converted local ID's GUID by running:

Set-MSOLuser -UserPrincipalName someUser@yourTenant.onmicrosoft.com -ImmutableID $immutableID

which will return the following error:

Set-MSOLuser : Uniqueness violation. Property: SourceAnchor.
At line:1 char:1
+ Set-MSOLuser -UserPrincipalName someUser@yourTenant.onmicro ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : OperationStopped: (:) [Set-MsolUser],
MicrosoftOnlineException
+ FullyQualifiedErrorId :
Microsoft.Online.Administration.Automation.UniquenessValidationException,Microsoft.Online.Administration.Automation.SetUser

which makes sense because you already have a synced ID with that same immutableID!

Run the command to show both your users (both the synced one as well as the one in the cloud) and show both the UPN and ObjectID.

Get-MsolUser -ReturnDeletedUsers -All -SearchString someUser@yourDomain.com | FL UserPrincipalName, ObjectID

At some point, move your synced object in your local AD from an OU that's synced over to an OU that's not synced. Or simply delete it from your local AD. Then sync your local AD to the cloud. This should have the effect of deleting the synced object. But you still can't reassign the old synced object's immutable ID to the cloud-only version until you also delete it from the recycle bin. For some reason, trying to stuff the value of the Object ID into a variable…

$objectID = Get-MsolUser -ReturnDeletedUsers -SearchString someUser@yourDomain.com | Select-Object ObjectID

…and then use the variable to try to delete it doesn't work:

Remove-MsolUser -ObjectId $objectID -RemoveFromRecycleBin -Force

you get an error:

Remove-MsolUser : Cannot bind parameter 'ObjectId'. Cannot convert the @{ObjectId=3540aa84-5a35-4b2c-bdb9-2671ff28ad9c} value of type Selected.Microsoft.Online.Administration.User to type System.Guid.
At line:1 char:27
+ Remove-MsolUser -ObjectId $objectID -RemoveFromRecycleBin -Force
+ ~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [Remove-MsolUser],
ParameterBindingException
+ FullyQualifiedErrorId :
CannotConvertArgumentNoMessage,Microsoft.Online.Administration.Automation.RemoveUser

Instead, you have to actually paste the value in from the command you ran above:

Remove-MsolUser -ObjectId 3540aa84-5a35-4b2c-bdb9-3782ff28ad9c -RemoveFromRecycleBin -Force

which works better. There's probably some way to simply use the variable rather than cutting and pasting the value into your subsequent command. Maybe this might work:

$objectID = (Get-MsolUser -ReturnDeletedUsers -SearchString someUser@yourDomain.com).ObjectID

I'll try that next time.

sync problems

Get-MsolDirSyncProvisioningError -ErrorCategory PropertyConflict -PropertyName UserPrincipalName
Get-MsolDirSyncProvisioningError -ErrorCategory PropertyConflict -PropertyName ProxyAddresses
Get-MsolUser -UserPrincipalName user@yourdomain.com | fl DirSyncProvisioningErrors

synced users, list

$syncedUsers = Get-MSOlUser -All | ? {$null -ne $_.LastDirSyncTime}
$syncedUsers.Count
$syncedUsers | select DisplayName, LastDirSyncTime | ogv

synced user in AAD has no corresponding user in AD and won't let you delete

Remove-MsolUser -UserPrincipalName dontWannaSeeYourFace@RoundHereNoMore.onmicrosoft.com -force

synced user, stop syncing

On the syncing server, stop synchronization

Set-ADSyncScheduler -SyncCycleEnabled $false

At this point, it's probably also a good idea to either move the local ID counterpart of this soon-to-be-liberated ID from an OU that syncs to another OU that doesn't. Or simply remove from local AD altogether. Either way, make sure you do this before you re-enable syncing below

On the cloud, get his immutable ID. We'll probably never need it. But just in case we change our mind, it's better to have it and not need it than need it and not have it.

Get-MsolUser -UserPrincipalName newly.ascended@master.com | Select-Object UserprincipalName,ImmutableID,LastDirSyncTime # T30kIiXhSEeE1eEPlfjZoA==

Get rid of his immutable ID

Get-MsolUser -UserPrincipalName newly.ascended@master.com | Set-MsolUser -ImmutableId $null

On the syncing server, resume synchronization

Set-ADSyncScheduler -SyncCycleEnabled $true

–T–

tenants, organizational relationships between – see organizational relationships between tenants, list

tenant, which one am I on?

This will list licenses with how many license you have (ActiveUnits) as well as how many consumed:

Get-MsolAccountSku

This will give the actual tenant name:

(Get-MsolAccountSku)[0].AccountSkuID.split(":")[0]

What we do above is load up all the licenses, pick the first instance of the returned results and then split off the tenant name from that - which is the first part of the string before the ":".

term 'xx' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again. (Exchange)

for example:

get-mailbox -ResultSize Unlimited

returns:

get-mailbox : The term 'get-mailbox' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
At line:1 char:1
+ get-mailbox -ResultSize Unlimited
+ ~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (get-mailbox:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException

need to run:

add-pssnapin *exchange* -erroraction SilentlyContinue

title case – see proper case

–U–

unlicensed users

Get-MsolUser -All -UnlicensedUsersOnly

unlicensed users, suppress entries from showing up in the Offline Address Book (OAB) or Global Address List (GAL) – see Global Address List (GAL), suppress entries

unlock user – see locked out

update Azure module

Update-Module -Name Azure -Verbose

failed, complaining

Update-Module : Module 'Azure' was not installed by using Install-Module, so it cannot be updated.

So, I had to specify the -Scope as CurrentUser. This installs the new version side-by-side with the old version.

Install-Module -Name Azure -Scope CurrentUser -Verbose -Force -AllowClobber

Without the -Force I got

WARNING: Version '1.3.2' of module 'Azure' is already installed at 'C:\Program Files (x86)\Microsoft SDKs\Azure\PowerShell\ServiceManagement\Azure'. To install version '5.3.0', run Install-Module and add the -Force parameter, this command will install version '5.3.0' side-by-side with version '1.3.2'.

I also had to add -AllowClobber because

PackageManagement\Install-Package : The following commands are already available on this system:'Get-AzureStorageContainerAcl,Start-CopyAzureStorageBlob,Stop-CopyAzureStorageBlob'. This module 'Azure.Storage' may override the existing commands. If you still want to install this module 'Azure.Storage', use -AllowClobber parameter.

After you install this way, if you check the version, you'll see both the old and the new show up. Also note that the install location is in your 'My Documents' instead of in the usual 'C:\Program Files (x86)\Microsoft SDKs\Azure\PowerShell\ServiceManagement' location

upper case, change to proper case – see proper case

usage location for all users

this sorts by domain name, display name

Get-MsolUser -All | Select-Object @{n="Dom";e={$_.UserPrincipalName.split("@")[1]}}, UsageLocation, UserPrincipalName, displayName | Sort-Object dom, displayName | ft dom, displayName, UsageLocation, UserPrincipalName

user history – see audit log, search

user, create

New-MsolUser -UserPrincipalName "SomeUser@yourDomain.com" -DisplayName "Some User" -FirstName "Some" -LastName "User"

or

$NewUserParams = @{
    'UserPrincipalName' = $UPN
    'DisplayName' = $DisplayName
    'FirstName' = $FirstName
    'LastName' = $LastName
    'Title' = $Title
    'Department' = $Department
    'Password' = $DefaultPassword
}
New-MsolUser @NewUserParams

user, create from CSV

Import-Csv -Path userList.csv | %{New-MsolUser -UserPrincipalName $_.UserPrincipalName -DisplayName $_.DisplayName -FirstName $_.FirstName -LastName $_.LastName -Department -Password $_.Password $_.Department -country $_.Country} | ogv

user created, when?

Get-MsolUser -UserPrincipalName 'newGuy@FastTurnover.com' | select WhenCreated

user, delete

Normally remove users through GUI interface. But sometimes the GUI is convinced that a particular onmicrosoft.com user is synced even though you see no trace of it in local AD. In this case, the following command sometimes works:

Remove-MsolUser -UserPrincipalName emperor@Palpatine.onmicrosoft.com -Force

use "-Force" above instead of "-Confirm"

or

Remove-AzureADUser -ObjectId "emperor@Palpatine.onmicrosoft.com"

user, does he exist?

succinctly

$UPN = "batman@BruceWayne.com"

new way that works now using Get-MgUser

if (Get-MgUser -UserPrincipalName $UPN -ErrorAction SilentlyContinue){"there IS a user for $UPN"} else {"there is NO user for $UPN"}

old way that no longer works using the deprecated Get-MsolUser

if (Get-MsolUser -UserPrincipalName $UPN -ErrorAction SilentlyContinue){"there IS a user for $UPN"} else {"there is NO user for $UPN"}

or a little more verbose (new way that works now using Get-MgUser)

$User = Get-MgUser -UserPrincipalName $UPN -ErrorAction SilentlyContinue -ErrorVariable errorVariable

old way that no longer works using the deprecated Get-MsolUser

$User = Get-MsolUser -UserPrincipalName $UPN -ErrorAction SilentlyContinue -ErrorVariable errorVariable

follow either of the statements above with:

If ($User -ne $Null)
{
    Write-Host "$UPN exists" -Foregroundcolor green
}
Else
{
    Write-Host "$UPN does not exist" -Foregroundcolor yellow
}

user, groups to which he belongs – see groups, show all groups to whom someone belongs

user info

old way that no longer works using the deprecated Get-MsolUser

Get-MsolUser -UserPrincipalName someUser@yourDomain.com | fl

new way that works now using Get-MgUser

Get-MgUser -UserID someUser@yourDomain.com | fl

But this doesn't give you many properties. To see all properties:

Get-MgUser -UserID 'someUser@yourDomain.com' | Select-Object *

user info

individual - use either UPN:

Get-MsolUser -userprincipalname test@yourdomain.com | fl

or objectID:

Get-MsolUser -ObjectId 81701046-cb37-439b-90ce-2afd9630af7d | fl

everyone:

Get-MsolUser | Sort-Object DisplayName,UserPrincipalName

users, list those that match a string

Get-MsolUser -SearchString 'jm'

users, list all sorted by domain – see domain, find all users - sort by domain

user locked out – see locked out

user, rename – see rename user

user, restore when UserPrincipalName has a domain not accepted by the tenant

Restore-MsolUser -UserPrincipalName someuser@baddomain.com -AutoReconcileProxyConflicts -NewUserPrincipalName someuser@tenantname.onmicrosoft.com

user, soft deleted - delete permanantly

Sometimes you run into a situation where you're trying to delete a user but the system claims some other, similarly named user is already clogging up the deleted users

Get-MsolUser -ReturnDeletedUsers -SearchString zombie@undead.com | FL UserPrincipalName, ObjectID

Sometimes, can't return a soft deleted user by name even though you see him right there. In that case, run:

Get-MsolUser -All -ReturnDeletedUsers | ft DisplayName, ObjectId

and grab the appropriate ObjectID so you can delete it that way. Whichever way you get it, once you have it, use the ObjectId to delete:

Remove-MsolUser -ObjectId 8b0b9ca0-a3cf-4444-9b1b-c8dc92e69261 -RemoveFromRecycleBin -Force

Now, finally, you can delete the object you orginally intended

Remove-MsolUser -UserPrincipalName zombie@undead.com -RemoveFromRecycleBin -Force

user soft deleted, restore when UserPrincipalName has a domain not accepted by the tenant

Restore-MsolUser -UserPrincipalName someUser@baddomain.com -AutoReconcileProxyConflicts -NewUserPrincipalName someUser@yourTenant.onmicrosoft.com

userPrincipalName

change:

Set-MsolUserPrincipalName -UserPrincipalName "becky.smith@yourcompany.onmicrosoft.com" -NewUserPrincipalName "becky.smith@yourcompany.com"

userPrincipalName

change individual:

Set-MsolUserPrincipalName -UserPrincipalName "becky.smith@yourcompany.onmicrosoft.com" -NewUserPrincipalName "becky.smith@yourcompany.com"

bulk change - in this case, change a recent batch of added users

Get-MsolUser -All | ?{$_.WhenCreated -gt "6/19/2019 5 pm"} | %{Set-MsolUserPrincipalName -UserPrincipalName $_.UserPrincipalName -NewUserPrincipalName "$($_.UserPrincipalName.split("@")[0])@yourDomain.com"}

–V–

version of Azure

Get-Module -ListAvailable -Name Azure -Refresh

or

Get-Module -ListAvailable -Name Azure -Refresh | Select-Object Name, Version, Path, PowerShellVersion

or

(Get-Module -ListAvailable -Name Azure -Refresh).Version

To update, see update Azure module

–W–

when was a user created – see user created, when?

Where am I? As in: which tenant am I on? – see which tenant am I on (right below)

Which tenant am I on? - the closest I can find is the command to list all the licenses that a tenant has available: Get-MsolAccountSku. This will return a list of license SKUs. Embedded in each AccountSkuId will be the tenant name before the :. Pretty hokey.

wild card search - sometimes when someone asks you to add someone to a "group", you don't really know whether they mean a distribution group, an email-enabled group, a Team or a shared mailbox. They often have no idea. So, sometimes you need to go on a fishing expedition to find out more.

email-enabled security groups or distribution groups

Get-AzureADGroup -SearchString "Tech" | select DisplayName

Teams

Teams search is retarded in that the purported wildcard only works for stuff at the beginning; if the string you're looking for is somewhere in the middle, forget it. The code below works around this.

$match = "Tech"
$AzureGroups = Get-AzureADmsGroup -All 100000 | ? {$_.Grouptypes -ne ""} | select ID,DisplayName,GroupTypes | Sort DisplayName
foreach ($azGroup in $AzureGroups)
{
    $DisplayName = $azGroup.DisplayName
    if ($DisplayName -match $match)
    {
        [pscustomObject]@{
        #ID = $azGroup.ID
        DisplayName = $DisplayName
        GroupTypes = $azGroup.GroupTypes
        }
    }
}

users (as in shared mailboxes)

Get-MsolUser -SearchString "Tech" | select DisplayName

–X–

–Y–

–Z–