Microsoft 365 – Self-service purchase

Self-service purchase gives users a chance to try out new technologies and develop solutions that ultimately benefit their larger organizations. Central procurement and IT teams have visibility to all users who buy and deploy self-service purchase solutions through the Microsoft 365 admin center. Admins can turn off self-service purchasing on a per product basis via PowerShell..

To read more about the Self-service purchase option, go to: Self-service purchase FAQ | Microsoft Docs

To disable the AllowSelfServicePurchase do the following:

#Install module
Install-Module -Name MSCommerce

#Import module
Import-Module -Name MSCommerce


#Get details
Get-MSCommerceProductPolicies -PolicyId AllowSelfServicePurchase

#Disable ProductID (or $True to enable)
Update-MSCommerceProductPolicy -PolicyId AllowSelfServicePurchase -ProductId CFQ7TTC0KP0N -Enabled $False

The following table shows the ProductID needed to enable or disable

Power Apps per userCFQ7TTC0KP0P
Power Automate per userCFQ7TTC0KP0N
Power Automate RPACFQ7TTC0KXG6
Power BI Premium (standalone)CFQ7TTC0KXG7
Project Plan 1CFQ7TTC0KXND
Project Plan 3CFQ7TTC0KXNC
Visio Plan 1CFQ7TTC0KXN9
Visio Plan 2CFQ7TTC0KXN8

source: Use AllowSelfServicePurchase for the MSCommerce PowerShell module | Microsoft Docs

Cannot Tenant to Teams Only upgrade mode in Teams Admin Center

When changing the coexistence mode to Teams only you get the following error: Please see the unsaved sections higlighted in red below:

We cannot see what the problem is when switching to teams only. With powershell it has better error messages. Use the following powershell to connect to teams and change the tennant

#Connect with the SkypeOnlineConnector
Import-Module SkypeOnlineConnector
$sfbSession = New-CsOnlineSession
Import-PSSession $sfbSession

#Change the tennant to TeamsOnly
Grant-CsTeamsUpgradePolicy -PolicyName UpgradeToTeams -Global

The following message appears:

This organization cannot be upgraded to TeamsOnly at the tenant level because there is an on-premise deployment of Skyp
e for Business detected in 1 or more of it sip domains

To change this use the following command:

Disable-CsOnlineSipDomain -Domain domainname

After all domains have been altered, you can change the tenant to TeamsOnly with Powershell or the GUI:


Error upgrading organization to TeamsOnly – TechNut

Chris Webb’s 365 Blog: Cannot set users or Tenant to Teams Only upgrade mode in Teams Admin Center (

Office 365 Hide mailboxes from the GAL

Export all mailboxes who are shown in the GAL

Get-Mailbox -ResultSize Unlimited | Where {$_.HiddenFromAddressListsEnabled -eq $false}| select UserPrincipalname, HiddenFromAddressListsEnabled | Export-Csv "c:\temp\gal.csv" -NoTypeInformation -Encoding UTF8

Show all private groups which are shown in the GAL (Teams groups are default hidden)

Get-UnifiedGroup | Where-Object {$_.AccessType -eq 'Private'} | select Displayname, PrimarySMTPAddress

To hide a single mailbox use the following command:

Set-Mailbox -Identity -HiddenFromAddressListsEnabled $true

To hide multiple mailboxes from the GAL, create a CSV file and use that as input to hide the mailboxes:

Import-Csv 'C:\Hide_Mailboxes.csv' | ForEach-Object {
$upn = $_."UserPrincipalName"
Set-Mailbox -Identity $upn -HiddenFromAddressListsEnabled $true

To hide a single group use the following command:

Set-UnifiedGroup <group> -HiddenFromAddressListsEnabled $true

Or hide all private groups at once:

Get-UnifiedGroup | Where-Object {$_.AccessType -eq 'Private'} | Set-UnifiedGroup -HiddenFromAddressListsEnabled $true


Find mailboxes hidden from the GAL using Powershell – MorganTechSpace

Hide Office 365 Group from GAL using Powershell – MorganTechSpace

Veeam: Your License consumption has been exceeding the license cap

When using Veeam backup for Microsoft Office 365 it is possible you get a license error during backup. This is because when you move a Microsoft 365 license to a new user, this needs a backup to and counts as a second Veeam license.

Normally this should not be a problem, you can use more Veeam licenses than you have (10% of extra licenses or 10 user accounts -whichever is greater-) to overcome this issue. And if a user does not have a restore point in the past 31 days the license will be available again. To see the more information about licenses check this page out: Licensing and License Types – Veeam Backup for Microsoft Office 365 Guide

If Veeam is not backing up anymore because you do not have enough license, you should purchase new ones or free some.

To revoke licenses without waiting you can use powershell. To revoke licenses you need to remove all of the user’s data from all the repositories prior to revoking the license.

Use the following procedure to remove user licenses: Remove User License in Veeam Backup for Microsoft Office 365

Use the following powershell command to check which data a user has on a repository:

$repository = Get-VBORepository -Name "REPOSITORY" 
$user = Get-VBOEntityData -Type User -Repository $repository -Name "" 


If the user has email existing on the repository, you will use the following section:

#This remove script is used for repositories containing users Email
#Fill in "REPOSITORY" with the name of the repository as it is showing in Veeam Backup for Office 365 and the email address of the user you wish to remove the data for ""

$repository = Get-VBORepository -Name "REPOSITORY" 
$user = Get-VBOEntityData -Type User -Repository $repository -Name "" 
Remove-VBOEntityData -Repository $repository -User $user -Mailbox -ArchiveMailbox -OneDrive -Sites
#Y will accept the deleting of data

Remove License

Once you have removed the user’s data from all configured repositories, then you can remove the license for this user. The below script will pull the organization you have added into the console and desired user to then release the license. If you missed a repository, the error will tell you on what repository you still have remaining data:

#If there is no more data for this user on any repository you can use the following to remove the license
#Fill in the domain name as it is shown in Veeam Office 365 "" and the users email address ""

$org = Get-VBOOrganization -Name ""
$licensedUser = Get-VBOLicensedUser -Organization $org -Name ""
Remove-VBOLicensedUser -User $licensedUser

No Email

If a user has no email data look at the following article to delete sharepoint or onedrive data: Remove User License in Veeam Backup for Microsoft Office 365

Failed To Disable The Mailbox Due To a Conflict In Directory Settings

If you have a conflict in directory settings between Microsoft 365 and your on premise location use the next procedure to solve this

Check if the affected mailbox in Exchange Online is an usermailbox

get-recipient user | fl recipient*

If not already, disable the user account in the on-premise environment.

disable-mailbox user

Wait (or force) until the changes replicate to Microsoft 365. Once it is synced, enable the on-premise object as an remote user mailbox

Enable-RemoteMailbox user -RemoteRoutingAddress

Wait (or force) until the sync to Microsoft 365 has been completed and then check the user. The error message should be gone and a license can be added

To backup a domain controller with powershell. Use the script below to place it on the network. This scripts requires the Windows Server Backup Feature and can be scheduled

Import-Module ServerManager
[string]$date = get-date -f 'yyyy-MM-dd'
$TestTargetUNC= Test-Path -Path $TargetUNC
if (!($TestTargetUNC)){
New-Item -Path $TargetUNC -ItemType directory
$WBadmin_cmd = "wbadmin.exe START BACKUP -backupTarget:$TargetUNC -systemState -noverify -vssCopy -quiet"
Invoke-Expression $WBadmin_cmd

If an error occurs:

Detailed error: The filename, directory name, or volume label syntax is incorrect. The backup of the system state failed 

Make sure all drivers are set correct. In my case changing the path of vsock did the trick. How to find the driver path? Open an elevated prompt and execute the following commands:

DiskShadow /L writers.txt
list writers detailed

Check the directories, in my case the string which was found was the following:

File List: Path = c:\windows\\systemroot\system32\drivers, Filespec = vsock.sys

To correct the path, open the Registry Editor and go to reg key HKLM\SYSTEM\CurrentControlSet\Services\vsock

Change the ImagePath value from


Run the backup again, it should say:

The backup operation successfully completed.
The backup of volume (C:) completed successfully.
The backup of the system state successfully completed [01.06.2020 09:52].


ACL script to determine folder permissions

To determine the ACL’s on a specific foldertree use the following script. It will display the Path, FileSystemRights, IsInherited, Name of the underlying folders.

$path = "\\server\path\"
$targetFile = "file.csv" # Not working yet

$foldersToQuery = Get-ChildItem $Path | Where {$_.PSIsContainer} | select -expandproperty FullName

   # Get access list, change any domain
foreach ($folder in $foldersToQuery) {
    $Access = (Get-Acl $Folder).Access |
      Select-Object @{n='Path';e={ $Folder }}, *,
          If ($_.IdentityReference -NotMatch "^(NT AUTH|BUILTIN|$($Env:ComputerName))") {
            $Searcher = [ADSISearcher]"(sAMAccountName=$($_.IdentityReference -Replace '^.+\\'))"
            $Searcher.PropertiesToLoad.AddRange(@("name", "distinguishedName", "objectClass"))
        } }} |
      Select-Object *, @{n='Name';e={ $_.ADObject.Properties['name'][0] }},
        @{n='DN';e={ $_.ADObject.Properties['distinguishedname'][0] }},
        @{n='Class';e={ ([String[]]$_.ADObject.Properties['objectclass'])[-1] }} -Exclude ADObject

      $Access | ForEach-Object {
        $Entry = $_
        If ($Entry.Class -eq 'group') {
          $Searcher = [ADSISearcher]"(memberOf:1.2.840.113556.1.4.1941:=$($Entry.DN))"
          $Searcher.PageSize = 1000
          $Searcher.PropertiesToLoad.AddRange(@("name", "distinguishedName", "objectClass"))
          $Searcher.FindAll() | ForEach-Object {
            $ADObject = $_
            $Entry | Select-Object *, @{n='Name';e={ $ADObject.Properties['name'][0] }},
              @{n='DN';e={ $ADObject.Properties['distinguishedname'][0] }},
              @{n='Class';e={ ([String[]]$ADObject.Properties['objectclass'])[-1] }} -Exclude Name, DN, Class
        } Else {
      } | ft Path, FileSystemRights, IsInherited, Name, class -AutoSize  


Microsoft Teams – Welcome Message

Microsoft teams can send welcome messages when a user is added to the teams. This behavior can be altered with powershell

The current setting for the team can be checked with powershell. To run this, you need to be connected to Microsoft 365. For Microsoft 365 management you can install the “Microsoft Exchange Online Powershell Module” from Microsoft 365.

Get-UnifiedGroup -Identity 'Group Name' | fl WelcomeMessageEnabled

The outcome should be True or False. When it is set to True, welcome messages will be send. To change this setting, so welcome messages are not send, change the value to false

set-UnifiedGroup -Identity 'Group Name' -UnifiedGroupWelcomeMessageEnabled:$False

Last Logon Date AD Users


The “lastLogon” Active Directory attribute is a property that is not replicated throughout the Domain Controllers.  This attribute is stored on the domain controller that received the authentication request and updated its property accordingly.  Because of this behavior, you have have experienced issues where Domain Controllers in other sites have old or empty lastLogon information.  This makes it difficult to be sure an account is truly inactive without verifying on each Domain Controller.  This can be a problem for environments that have tens or hundreds of Domain Controllers across its enterprise.

I was faced with a problem of writing a script, and like many, developed a seemingly simple solution.  The problem was the runtime was very long for the amount of Domain Controllers / User accounts for the environment.  I sought out to find the solution to this exceedingly long runtime, and I finally did.

This PowerShell will query each of your Domain Controllers only once and produce the most recent logon date/time.

To protect the informaiton about my directory, lets say my environment is between 5 and 10 Domain Controllers, with between 15 – 25k user accounts.  My runtime is between 4-5 minutes for the entire directory!  Which is a far cry faster than the hours other methods produce.

By default, this script will return the entire directory ($Username=*) and save to a CSV ($FileName).  You may call the function with a -Username to return a single account, and this will output on screen instead of a file.

    The purpose of this PowerShell script is to collect the last logon  
    for user accounts on each DC in the domain, evaluate, and return the 
    most recent logon value. 
        Author:   Jeremy Reeves 
        Modified: 02/14/2018 
        Notes:    Must have RSAT Tools if running on a workstation 
Note: Added enabled true/false to output
Import-Module ActiveDirectory 
function Get-ADUsersLastLogon($Username="*") { 
    $FilePath_Prefix = "C:\temp\UserLastLogon-" 
    function Msg ($Txt="") { 
        Write-Host "$([DateTime]::Now)    $Txt" 
    #Cycle each DC and gather user account lastlogon attributes 
    $List = @() #Define Array 
    (Get-ADDomain).ReplicaDirectoryServers | Sort | % { 
        $DC = $_ 
        Msg "Reading $DC" 
        $List += Get-ADUser -Server $_ -Filter "samaccountname -like '$Username'" -Properties LastLogon | Select samaccountname,lastlogon,enabled,@{n='DC';e={$DC}} 
    Msg "Sorting for most recent lastlogon" 
    $LatestLogOn = @() #Define Array 
    $List | Group-Object -Property samaccountname | % { 
        $LatestLogOn += ($_.Group | Sort -prop lastlogon -Descending)[0] 
    if ($Username -eq "*") { #$Username variable was not set.    Running against all user accounts and exporting to a file. 
        $FileName = "$FilePath_Prefix$([DateTime]::Now.ToString("yyyyMMdd-HHmmss")).csv" 
        try { 
            $LatestLogOn | Select samaccountname, lastlogon, @{n='lastlogondatetime';e={[datetime]::FromFileTime($_.lastlogon)}}, Enabled, DC | Export-CSV -Path $FileName -NoTypeInformation -Force 
            Msg "Exported results. $FileName" 
        } catch { 
            Msg "Export Failed. $FileName" 
    } else { #$Username variable was set, and may refer to a single user account. 
        if ($LatestLogOn) { $LatestLogOn | Select samaccountname, @{n='lastlogon';e={[datetime]::FromFileTime($_.lastlogon)}}, Enabled, DC | FT } else { Msg "$Username not found." }