Script Repository

Set account expiration date via meeting in Exchange

June 23, 2020

The script allows you to expire user accounts via meetings in Exchange. To use the script, you need to appoint meetings for a certain mailbox. The account you want to expire must be specified in a meeting subject, and the desired expiration date must be the start date of a meeting. If an error occurs, the script sends an email reply to the meeting appointer with error details.

To be able to use the script, download and install Microsoft Exchange Web Services Managed API on the computer where Adaxes service runs.

The meetings must be appointed for the mailbox specified in the Run As section of the Run a program or PowerShell script action used to run the script.

To use the script in your environment, create a Scheduled Task for the Domain-DNS object type that runs the script. When creating the task, include any of your AD domains in the Activity Scope.


  • $exchangeWebServiceDllPath - specifies the full path to the Microsoft Exchange Web Services dll module;
  • $exchangeServer - specifies the fully qualified domain name (FQDN) of your Exchange Server. If you specify $NULL for this parameter, the connection will be made to Exchnage Online;
  • $subjectSearchPhrase - specifies the search phrase in the appointment notification message subject. The script will search meetings by this phrase. For example, if you specify User leaving: for $subjectSearchPhrase and want to expire the account of user John Doe, the meeting subject can be User leaving: John Doe;
  • $mailboxMailAddress - specifies the email address of the mailbox where meetings will be appointed;
  • $errorEmailSubject - specifies the subject of emails with error messages;
  • $errorEmailHeader - specifies the header of emails with error messages;
  • $errorEmailFooter - specifies the footer of emails with error messages.
See Also: Appoint meeting on user account expiration day.
Edit Remove
# Exchange settings
$exchangeWebServiceDllPath = "C:\Program Files\Microsoft\Exchange\Web Services\2.2\Microsoft.Exchange.WebServices.dll" # TODO: modify me
$exchangeServer = "" # TOOD: modify me. If $NULL, connect to Exchange Online
$subjectSearchPhrase = "User leaving:" # TOOD: modify me
$mailboxMailAddress = "" # TOOD: modify me

# Email settings
$errorEmailSubject = "Failed Account Expiration Tasks" # TODO: modify me
$errorEmailHeader = "<h2><b>Errors that occurred when processing your requests to expire user accounts</b></h2>" # TODO: modify me
$errorEmailFooter = "<hr /><p><i>Please do not reply to this e-mail, it has been sent to you for notification purposes only.</i></p>" # TODO: modify me

Import-Module $exchangeWebServiceDllPath

function SearchObjects($filter, $propertiesToLoad)
    $searcher = $Context.BindToObject("Adaxes://rootDSE")
    $searcher.SearchFilter = $filter
    $searcher.SearchScope = "ADS_SCOPE_SUBTREE"
    $searcher.PageSize = 500
    $searcher.ReferralChasing = "ADS_CHASE_REFERRALS_NEVER"
    $searcher.VirtualRoot = $True
        $searchResultIterator = $searcher.ExecuteSearch()
        $searchResults = $searchResultIterator.FetchAll()
        return ,$searchResults
        # Release resources
        if ($searchResultIterator){ $searchResultIterator.Dispose() }

function AddErrorToReport ($message, $initiatorMail, $startDate, $errorReports)
    if (-not($errorReports.ContainsKey($initiatorMail)))
        $errorReports.Add($initiatorMail, (New-Object "System.Text.StringBuilder"))
    $report = $errorReports[$initiatorMail]

if ($exchangeServer -eq $NULL)
    # Connect to Exchange Online via the Exchange Web Services API
    $exchangeWebService = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService
    $microsoft365Cred = $Context.GetOffice365Credential()
    if ($microsoft365Cred -eq $NULL)
        $Context.LogMessage("Credentials are not specified for the Microsoft 365 Tenant that the mailbox belongs to or the mailbox is not associated with any Microsoft 365 Tenant", "Warning")
    $exchangeWebService.Credentials = New-Object System.Net.NetworkCredential($microsoft365Cred.Username, $microsoft365Cred.GetNetworkCredential().Password)
    $exchangeWebService.Url = ""
    # Connect to Exchange on-premises via the Exchange Web Services API
    $exchangeWebService = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010)
    $exchangeWebService.Url = "https://$exchangeServer/ews/exchange.asmx"

# Build filter to find the necessary mail message
$searchFilter = New-Object Microsoft.Exchange.WebServices.Data.SearchFilter+ContainsSubstring([Microsoft.Exchange.WebServices.Data.EmailMessageSchema]::Subject, $subjectSearchPhrase)
$searchFilterCollection = New-Object Microsoft.Exchange.WebServices.Data.SearchFilter+SearchFilterCollection([Microsoft.Exchange.WebServices.Data.LogicalOperator]::And)

# Bind to the Inbox folder
$folderID = New-Object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Calendar, $mailboxMailAddress)
$inbox = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($exchangeWebService, $folderID)

# Set order
$itemView = New-Object Microsoft.Exchange.WebServices.Data.ItemView(1000)
$itemView.OrderBy.Add([Microsoft.Exchange.WebServices.Data.ItemSchema]::DateTimeReceived, [Microsoft.Exchange.WebServices.Data.SortDirection]::Descending)

$mailSearchResults = $inbox.FindItems($searchFilterCollection, $itemView)

if ($mailSearchResults.Items.Count -eq 0)
    return # No messages to process

# Process meetings
$users = @{}
$errorReports = @{}
foreach ($item in $mailSearchResults.Items)
    $userFullName = $item.Subject.Replace($subjectSearchPhrase, "").Trim()
    # Find user
    $searchResults = SearchObjects "(&(sAMAccountType=805306368)(cn=$userFullName))" @()

    if ($searchResults.Length -eq 0)
        $message = "User '$userFullName' not found"
        $Context.LogMessage($message, "Warning")
        AddErrorToReport $message $item.Organizer.Address $item.Start $errorReports
    elseif ($searchResults.Length -gt 1)
        $message = "Found more than one user with name '$userFullName'"
        $Context.LogMessage($message, "Warning")
        AddErrorToReport $message $item.Organizer.Address $item.Start $errorReports
    if (-not($users.ContainsKey($userFullName)))
        $users.Add($userFullName, @{
            "SearchResult" = $searchResults[0]
            "ExpirationDate" = @($item.Start)
    elseif ($users[$userFullName].ExpirationDate -lt $item.Start)
        $users[$userFullName].ExpirationDate = $item.Start

# Update users
foreach ($userFullName in $users.Keys)
    # Bind to user
    $userInfo = $users[$userFullName]
    $user = $Context.BindToObject($userInfo.SearchResult.AdsPath)
    # Set account expiration date
    $user.Put("accountExpires", $userInfo.ExpirationDate)

# Send error reports to failed meeting request owners
foreach ($initiatorMail in $errorReports.Keys)
    # Build html
    $html = $errorEmailHeader + "<ul>" + $errorReports[$initiatorMail].ToString() + "</ul>" + $errorEmailFooter
    # Send mail
    $Context.SendMail($initiatorMail, $errorEmailSubject, $NULL, $html)

Comments ( 0 )
No results found.
Leave a comment