So our process is made up of the following 4 'Steps':
We created the following business rule (Note: 'Deprovision Date' is an adm-customattributedate field that we renamed)
if the 'Provide Manager Access to MB (Home Folder always provided)' property has changed AND
the User is not located under the 'Previous Employee Mailboxes (stcu.local\STCU\Shared Mailboxes)' container AND
the 'Deprovision Date' property has changed then
action: Run Powershell script 'create a scheduled task to de-provision the user' for the user
action: Modify the User: set Account Expires to '%adm-customattributeDate1, +5m%'
action: send e-mail notification (user deactivation scheduled by %initiator%)
the Script within 'create a scheduled task to de-provision the user' is as follows:
# Scheduled task settings
$containerName = "Deprovisioned User Processing" #must match a folder under scheduled tasks
$taskName = "Initial Deprovision - %username%"
$taskDescription = "This will process the disabling of the user account and provide Manager with access"
$deleteTaskAfterExecution = $True
$now = (Get-Date).addMinutes(5)
if("%adm-CustomAttributeDate1%" -lt $now){
$ProcessTime = $now.addminutes(5)
}else {
$ProcessTime = "%adm-CustomAttributeDate1%"
}
# Script for action
$scriptDescription = "Process Deprovisioning"
$scriptToExecute = {
[Reflection.Assembly]::LoadWithPartialName("Softerra.Adaxes.Adsi")
Import-Module Adaxes
$admNS = New-Object "Softerra.Adaxes.Adsi.AdmNamespace"
$admService = $admNS.GetServiceDirectly("localhost")
$userDN = (Get-AdmUser -Identity "%objectGUID%" -AdaxesService "localhost").DistinguishedName
$user = $admService.OpenObject("Adaxes://$userDN", $NULL, $NULL, 0)
$commandID = "{fb93226e-d3e7-4b97-afde-5262b3c2b931}" #This is a Custom Command (deprovision step 1) and the GUID will need to be changed to match your environment
$user.executecustomCommand($commandID)
}
function CheckNameForUnique($taskPath)
{
try
{
$task = $Context.BindToObject($taskPath)
return $False
}
catch
{
return $True
}
}
# Bind to the Scheduled Tasks container
$scheduledTasksPath = $Context.GetWellKnownContainerPath("ScheduledTasks")
$scheduledTasksPathObj = New-Object "Softerra.Adaxes.Adsi.AdsPath" $scheduledTasksPath
$containerPath = $scheduledTasksPathObj.CreateChildPath("CN=$containerName")
$container = $Context.BindToObject($containerPath)
# If the task name is not unique, generate a unique one
$uniqueName = $taskName
for ($i = 1; $True; $i++)
{
$taskPath = $containerPath.CreateChildPath("CN=$uniqueName")
if (CheckNameForUnique $taskPath)
{
break
}
$uniqueName = "$taskName`_$i"
}
# Create a Scheduled Task
$task = $container.Create("adm-ScheduledTask", "CN=$uniqueName")
$task.ObjectType = "domainDNS"
$task.Description = $taskDescription
$task.Disabled = $False
$task.ExecutionMoment = "ADM_BUSINESSRULEEXECMOMENT_BEFORE"
$task.OperationType = "none"
$task.DeleteTaskAfterExecution = $deleteTaskAfterExecution
$recurrencePattern = $task.GetRecurrencePattern()
$recurrencePattern.RecurrenceType = "ADM_RECURRENCEPATTERNTYPE_ONCE"
$recurrencePattern.PatternStartDateTime = $ProcessTime
$task.SetRecurrencePattern($recurrencePattern)
$task.SetInfo()
# Define actions and conditions
# Create a new set of actions and conditions
$actionsAndConditions = $task.ConditionedActions.Create()
$actionsAndConditions.ConditionsLogicalOperation = "ADM_LOGICALOPERATION_AND"
$actionsAndConditions.SetInfo()
# Add Run PowerShell Script action
$action = $actionsAndConditions.Actions.CreateEx("adm-RunScriptAction")
$action.ExecutionOptions = "ADM_ACTIONEXECUTIONOPTIONS_SYNC"
$scriptAction = $action.GetAction()
$scriptAction.ScriptType = "ADM_SCRIPTTYPE_POWERSHELL"
$scriptAction.ScriptDescription = $scriptDescription
$scriptAction.Script = $scriptToExecute.ToString()
$action.SetAction($scriptAction)
$action.SetInfo()
$actionsAndConditions.Actions.Add($action)
# Add the set to the Scheduled Task
$task.ConditionedActions.Add($actionsAndConditions)
# Set the scope of activity to All Objects
$scopeItem = $task.ActivityScopeItems.Create()
$scopeItem.BaseObject = $NULL
$scopeItem.Type = "ADM_SCOPEBASEOBJECTTYPE_ALL_DIRECTORY"
$scopeItem.Inheritance = "ADS_SCOPE_SUBTREE"
$scopeItem.Exclude = $False
$scopeItem.SetInfo()
$task.ActivityScopeItems.Add($scopeItem)
The Custom Command deprovision user step 1 is configured as follows:
If the User is not located under the 'Previous Employee Mailboxes ([domainpath to location to house 'old mailboxes']\)' container then
Action:Execute custom command 'Convert Mailbox to Shared (Sub-routines)' for the User
$mailboxType = "Shared" # TODO: uncomment the type you need
# $mailboxType = "Room"
# $mailboxType = "Equipment"
# Get the object ID in Office 365
try
{
$objectId = [Guid]$Context.TargetObject.Get("adm-O365ObjectId")
}
catch
{
$Context.LogMessage("The user doesn't have an Office 365 account", "Warning")
return
}
try
{
# Connect to Exchange Online
$session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri "https://outlook.office365.com/powershell-liveid/" -Credential $Context.GetOffice365Credential() -Authentication Basic -AllowRedirection
Import-PSSession $session -AllowClobber -DisableNameChecking
# Change mailbox type
Set-Mailbox $objectId.ToString() -Type $mailboxType
}
catch
{
$Context.LogMessage($_.Exception.Message, "Warning")
}
finally
{
# Close the remote session and release resources
Remove-PSSession $session
}
Action:Move the User to 'Previous Employee Mailboxes ([domainpath to location to house 'old mailboxes']\)'
Action:Reset password for the User
Action:Modify the User: disable the account
Action:Deactivate Office 365 account of the User: revoke all licenses
Action:Run PowerShell script 'Remove User from All Groups' for the User
# E-mail message settings
$to = "helpdeskemail@company.com" # TODO: modify me
$subject = "List of groups user '%name%' has been removed from" # TODO: modify me
$reportHeader = "<b>User {0} has been removed from the following groups:</b><br/>" # TODO: modify me
$reportFooter = "<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
# Get the default Web Interface address
$webInterfaceAddress = "%adm-WebInterfaceUrl%"
if ([System.String]::IsNullOrEmpty($webInterfaceAddress))
{
$Context.LogMessage("Default web interface address not set for Adaxes service. For details, see http://www.adaxes.com/help/?HowDoI.ManageService.RegisterWebInterface.html", "Warning")
}
# Add link to user
$userGuid = [Guid]$Context.TargetObject.Get("objectGuid")
$reportHeader = [System.String]::Format($reportHeader, "<a href='$webInterfaceAddress`ViewObject.aspx?guid=$userGuid'>%name%</a>")
# Get all groups user is a direct member of
$groupGuidsBytes = $Context.TargetObject.GetEx("adm-DirectMemberOfGuid")
$totalRemovedGroups = $groupGuidsBytes.Length - 1
if ($totalRemovedGroups -eq 0)
{
$Context.LogMessage("This user is a member only the primary group", "Information")
return
}
# Get the Primary Group ID
$primaryGroupId = $Context.TargetObject.Get("primaryGroupID")
$reportHeader += "<ol>"
foreach ($groupGuidBytes in $groupGuidsBytes)
{
# Bind to the group
$groupGuid = [Guid]$groupGuidBytes
$groupPath = "Adaxes://<GUID=$groupGuid>"
$group = $Context.BindToObject($groupPath)
# Skip the group if it is the user's Primary Group
if ($group.Get("primaryGroupToken") -eq $primaryGroupId)
{
continue
}
# Remove user from the group
$group.Remove($Context.TargetObject.AdsPath)
# Add group to the report
$groupDisplayName = [Softerra.Adaxes.Utils.ObjectNameHelper]::GetObjectName($groupPath, "IncludeParentPath")
$reportHeader += "<li><a href='$webInterfaceAddress`ViewObject.aspx?guid=$groupGuid'>$groupDisplayName</a></li>"
}
$reportHeader += "</ol>"
# Build report
$htmlReport = $reportHeader + "<br/>Total remove groups:$totalRemovedGroups" + $reportFooter
# Send mail
$Context.SendMail($to, $subject, $NULL, $htmlReport)
Action:Execute custom command 'Copy Users Home folder to manager (Sub-routines)' for the User
[Reflection.Assembly]::LoadWithPartialName("Softerra.Adaxes.Adsi")
$userDN = "%distinguishedName%"
$managerUserName = "%adm-ManagerUserName%"
$managerUserName = $managerUserName.Split("@")
$destDir = "\\somenetworkshare\$($managerUserName[0])\PreviousEmployeeData" #TODO Modify Me
$archivePath = "$destDir\%username%\%username%.zip"
$compressionLevel = 1
#Create Backup folder if not exist
If (!(Test-Path $destDir)) {
New-Item -Path $destDir -ItemType Directory
}
If (!(Test-Path -Path "$destDir\%username%")){
New-Item -Path "$destDir\%username%" -ItemType Directory
}
# Connect to Adaxes service
$admNS = New-Object "Softerra.Adaxes.Adsi.AdmNamespace"
$admService = $admNS.GetServiceDirectly("localhost")
# Bind to the user
$user = $admService.OpenObject("Adaxes://$userDN", $NULL, $NULL, 0)
try
{
# Archive the user's home folder
$user.ArchiveHomeDirectory($archivePath, $compressionLevel)
}
catch
{
$Context.LogMessage("Archive Home Directory Failed", "Error")
}
Action:Modify the User: clear msRTCSIP-PrimaryHomeServer, clear msRTCSIP-UserEnabled, clear msRTCSIP-OptionFlags, clear msRTCSIP-PrimaryUserAddress
Action:Modify the User: set Account Expires to '%accountExpires,+31d%'
Action:Delete the home directory of the User
Action:Run PowerShell script 'Set Manager Notification Scheduled Task' for the User
# Scheduled task settings
$containerName = "Deprovisioned User Processing" #must match a folder under scheduled tasks
$taskName = "Notify Manager 10days to deprovision - %username%"
$taskDescription = "This will process the disabling of the user account and provide Manager with access"
$deleteTaskAfterExecution = $True
$ProcessTime = (Get-Date).adddays(20)
# Script for action
$scriptDescription = "Notify Manager of impending user deletion" # TODO: modify me
$scriptToExecute = {
[Reflection.Assembly]::LoadWithPartialName("Softerra.Adaxes.Adsi")
Import-Module Adaxes
$admNS = New-Object "Softerra.Adaxes.Adsi.AdmNamespace"
$admService = $admNS.GetServiceDirectly("localhost")
$userDN = (Get-AdmUser -Identity "%objectGUID%" -AdaxesService "localhost").DistinguishedName
$user = $admService.OpenObject("Adaxes://$userDN", $NULL, $NULL, 0)
$commandID = "{b4b66610-be71-403a-a6b7-8bcf51d200ef}" #Custom Command GUID Deprovision step 2 TODO: Change Me
$user.executecustomCommand($commandID)
}
function CheckNameForUnique($taskPath)
{
try
{
$task = $Context.BindToObject($taskPath)
return $False
}
catch
{
return $True
}
}
# Bind to the Scheduled Tasks container
$scheduledTasksPath = $Context.GetWellKnownContainerPath("ScheduledTasks")
$scheduledTasksPathObj = New-Object "Softerra.Adaxes.Adsi.AdsPath" $scheduledTasksPath
$containerPath = $scheduledTasksPathObj.CreateChildPath("CN=$containerName")
$container = $Context.BindToObject($containerPath)
# If the task name is not unique, generate a unique one
$uniqueName = $taskName
for ($i = 1; $True; $i++)
{
$taskPath = $containerPath.CreateChildPath("CN=$uniqueName")
if (CheckNameForUnique $taskPath)
{
break
}
$uniqueName = "$taskName`_$i"
}
# Create a Scheduled Task
$task = $container.Create("adm-ScheduledTask", "CN=$uniqueName")
$task.ObjectType = "domainDNS"
$task.Description = $taskDescription
$task.Disabled = $False
$task.ExecutionMoment = "ADM_BUSINESSRULEEXECMOMENT_BEFORE"
$task.OperationType = "none"
$task.DeleteTaskAfterExecution = $deleteTaskAfterExecution
$recurrencePattern = $task.GetRecurrencePattern()
$recurrencePattern.RecurrenceType = "ADM_RECURRENCEPATTERNTYPE_ONCE"
$recurrencePattern.PatternStartDateTime = $ProcessTime
$task.SetRecurrencePattern($recurrencePattern)
$task.SetInfo()
# Define actions and conditions
# Create a new set of actions and conditions
$actionsAndConditions = $task.ConditionedActions.Create()
$actionsAndConditions.ConditionsLogicalOperation = "ADM_LOGICALOPERATION_AND"
$actionsAndConditions.SetInfo()
# Add Run PowerShell Script action
$action = $actionsAndConditions.Actions.CreateEx("adm-RunScriptAction")
$action.ExecutionOptions = "ADM_ACTIONEXECUTIONOPTIONS_SYNC"
$scriptAction = $action.GetAction()
$scriptAction.ScriptType = "ADM_SCRIPTTYPE_POWERSHELL"
$scriptAction.ScriptDescription = $scriptDescription
$scriptAction.Script = $scriptToExecute.ToString()
$action.SetAction($scriptAction)
$action.SetInfo()
$actionsAndConditions.Actions.Add($action)
# Add the set to the Scheduled Task
$task.ConditionedActions.Add($actionsAndConditions)
# Set the scope of activity to All Objects
$scopeItem = $task.ActivityScopeItems.Create()
$scopeItem.BaseObject = $NULL
$scopeItem.Type = "ADM_SCOPEBASEOBJECTTYPE_ALL_DIRECTORY"
$scopeItem.Inheritance = "ADS_SCOPE_SUBTREE"
$scopeItem.Exclude = $False
$scopeItem.SetInfo()
$task.ActivityScopeItems.Add($scopeItem)
Action:Send e-mail notification (User has been deactivated)
If the 'Provide Manager Access to MB (Home Folder always provided)' property equals 'True' AND
If the 'Manager' property is not empty AND
If the User has an Exchange mailbox then
Action:Modify mailbox settings for the User: modify Mailbox Rights (add '%manager% (allow 'Full mailbox access')')
Action:Modify mailbox settings for the User: set Hide from Exchange address lists to 'True'
Action:Send e-mail notification (User Deactivation)
If the 'Provide Manager Access to MB (Home Folder always provided)' property equals 'False' AND
If the 'Manager' property is not empty then
Action: Send e-mail notification (User Deactivation)
Custom Command Step 2 is as follows:
If the 'Provide Manager Access to MB (Home Folder always provided)' property equals 'True' then
Action: Send e-mail notification (User Deactivation)
Always
Action: Run PowerShell script 'Delete User (send to helpdesk for approval)' for the User
# Scheduled task settings
$containerName = "Deprovisioned User Processing" #must match a folder under scheduled tasks
$taskName = "Send to Helpdesk to confirm delete - %username%"
$taskDescription = "This will process the disabling of the user account and provide Manager with access"
$deleteTaskAfterExecution = $True
$ProcessTime = (Get-Date).AddDays(11)
# Script for action
$scriptDescription = "Delete Account and send to helpdesk for confirmation" # TODO: modify me
$scriptToExecute = {
[Reflection.Assembly]::LoadWithPartialName("Softerra.Adaxes.Adsi")
Import-Module Adaxes
$admNS = New-Object "Softerra.Adaxes.Adsi.AdmNamespace"
$admService = $admNS.GetServiceDirectly("localhost")
$userDN = (Get-AdmUser -Identity "%objectGUID%" -AdaxesService "localhost").DistinguishedName
$user = $admService.OpenObject("Adaxes://$userDN", $NULL, $NULL, 0)
$commandID = "{918f9c02-a91b-428e-88f7-6d4a6e3d5ae2}" #Custom Command GUID deprovision Step 3 TODO:Modify Me
$user.executecustomCommand($commandID)
}
function CheckNameForUnique($taskPath)
{
try
{
$task = $Context.BindToObject($taskPath)
return $False
}
catch
{
return $True
}
}
# Bind to the Scheduled Tasks container
$scheduledTasksPath = $Context.GetWellKnownContainerPath("ScheduledTasks")
$scheduledTasksPathObj = New-Object "Softerra.Adaxes.Adsi.AdsPath" $scheduledTasksPath
$containerPath = $scheduledTasksPathObj.CreateChildPath("CN=$containerName")
$container = $Context.BindToObject($containerPath)
# If the task name is not unique, generate a unique one
$uniqueName = $taskName
for ($i = 1; $True; $i++)
{
$taskPath = $containerPath.CreateChildPath("CN=$uniqueName")
if (CheckNameForUnique $taskPath)
{
break
}
$uniqueName = "$taskName`_$i"
}
# Create a Scheduled Task
$task = $container.Create("adm-ScheduledTask", "CN=$uniqueName")
$task.ObjectType = "domainDNS"
$task.Description = $taskDescription
$task.Disabled = $False
$task.ExecutionMoment = "ADM_BUSINESSRULEEXECMOMENT_BEFORE"
$task.OperationType = "none"
$task.DeleteTaskAfterExecution = $deleteTaskAfterExecution
$recurrencePattern = $task.GetRecurrencePattern()
$recurrencePattern.RecurrenceType = "ADM_RECURRENCEPATTERNTYPE_ONCE"
$recurrencePattern.PatternStartDateTime = $ProcessTime
$task.SetRecurrencePattern($recurrencePattern)
$task.SetInfo()
# Define actions and conditions
# Create a new set of actions and conditions
$actionsAndConditions = $task.ConditionedActions.Create()
$actionsAndConditions.ConditionsLogicalOperation = "ADM_LOGICALOPERATION_AND"
$actionsAndConditions.SetInfo()
# Add Run PowerShell Script action
$action = $actionsAndConditions.Actions.CreateEx("adm-RunScriptAction")
$action.ExecutionOptions = "ADM_ACTIONEXECUTIONOPTIONS_SYNC"
$scriptAction = $action.GetAction()
$scriptAction.ScriptType = "ADM_SCRIPTTYPE_POWERSHELL"
$scriptAction.ScriptDescription = $scriptDescription
$scriptAction.Script = $scriptToExecute.ToString()
$action.SetAction($scriptAction)
$action.SetInfo()
$actionsAndConditions.Actions.Add($action)
# Add the set to the Scheduled Task
$task.ConditionedActions.Add($actionsAndConditions)
# Set the scope of activity to All Objects
$scopeItem = $task.ActivityScopeItems.Create()
$scopeItem.BaseObject = $NULL
$scopeItem.Type = "ADM_SCOPEBASEOBJECTTYPE_ALL_DIRECTORY"
$scopeItem.Inheritance = "ADS_SCOPE_SUBTREE"
$scopeItem.Exclude = $False
$scopeItem.SetInfo()
$task.ActivityScopeItems.Add($scopeItem)
Custom Command deprovision step 3 is as follows:
Always
Action: Execute custom command 'Remove all Future appointments created by a user (Sub-routines)' for the User
$startDateProperty = "adm-CurrentDateTime" # Delete meetings starting today.
$endDateProperty = "$NULL" # Delete All Future Meetings
$exchangeWebServiceDllPath = "C:\Program Files\Microsoft\Exchange\Web Services\2.0\Microsoft.Exchange.WebServices.dll"
Import-Module $exchangeWebServiceDllPath
function GetDates ($property, $addDays)
{
try
{
$value = $Context.TargetObject.Get($property)
if ($value -is [Softerra.Adaxes.Adsi.AdsLargeInteger])
{
$value = [DateTime]::FromFiletime([Int64]::Parse($value))
}
$value = New-Object "System.Datetime" $value.Year, $value.Month, ($value.Day + $addDays), 0, 0, 0, ([System.DateTimeKind]::Local)
}
catch
{
$value = $NULL
}
return $value
}
function ClearProperties ($propertiesName)
{
foreach ($name in $propertiesName)
{
if (($name -eq $NULL) -or
($name -eq "adm-CurrentDateTime"))
{
continue
}
$Context.TargetObject.Put($name, $NULL)
$Context.TargetObject.SetInfoEx(@($name))
}
}
# Check whether the user has mailbox in Exchange Online
if (-not($Context.TargetObject.RecipientType -eq "ADM_EXCHANGERECIPIENTTYPE_MAILBOXENABLED" -and `
$Context.TargetObject.RecipientLocation -eq "ADM_EXCHANGERECIPIENTLOCATION_OFFICE365"))
{
return
}
# Get search parameters
$startDate = GetDates $startDateProperty 0
$endDate = GetDates $endDateProperty 1
# Get primary SMTP Address
$mailboxParams = $Context.TargetObject.GetMailParameters()
$emailAddresses = $mailboxParams.EmailAddresses
$primarySMTPAddress = $NULL
for ($i = 0; $i -lt $emailAddresses.Count; $i++)
{
$emailAddress = $emailAddresses.GetAddress($i,[ref]"ADS_PROPERTY_NONE")
if ($emailAddress.IsPrimary -and $emailAddress.Prefix -eq "smtp")
{
$primarySMTPAddress = $emailAddress.Address
break
}
}
if ([System.String]::IsNullOrEmpty($primarySMTPAddress))
{
$Context.LogMessage("Cannot remove meetings scheduled by the user because the user's mailbox doesn't have a primary SMTP address.", "Warning")
return
}
# Connect to Exchange Online via the Exchange Web Services API
$exchangeWebService = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService
$office365Cred = $Context.GetOffice365Credential()
$exchangeWebService.Credentials = New-Object System.Net.NetworkCredential($office365Cred.Username, $office365Cred.GetNetworkCredential().Password)
$exchangeWebService.Url = "https://outlook.office365.com/EWS/Exchange.asmx"
# Get the user's calendar
$calendarFolderId = New-Object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Calendar, $primarySMTPAddress)
$calendarFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($exchangeWebService, $calendarFolderId)
$itemView = New-Object Microsoft.Exchange.WebServices.Data.ItemView(500)
$propertySet = New-Object Microsoft.Exchange.WebServices.Data.PropertySet(
[Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties,
[Microsoft.Exchange.WebServices.Data.AppointmentSchema]::Organizer)
do
{
$searchResult = $calendarFolder.FindItems($itemView)
foreach ($item in $searchResult.Items)
{
if (!($item.IsMeeting))
{
continue # Not a meeting
}
$item.Load($propertySet)
if ($startDate -eq $NULL -and $endDate -eq $NULL)
{
$item.Delete([Microsoft.Exchange.WebServices.Data.DeleteMode]::HardDelete)
}
elseif (($startDate -eq $NULL) -and
($item.Start -lt $endDate))
{
$item.Delete([Microsoft.Exchange.WebServices.Data.DeleteMode]::HardDelete)
}
elseif (($endDate -eq $NULL) -and
($item.Start -ge $startDate))
{
$item.Delete([Microsoft.Exchange.WebServices.Data.DeleteMode]::HardDelete)
}
elseif (($startDate -ne $NULL) -and
($endDate -ne $NULL) -and
($item.Start -ge $startDate) -and
($item.Start -lt $endDate))
{
$item.Delete([Microsoft.Exchange.WebServices.Data.DeleteMode]::HardDelete)
}
}
$itemView.Offset = $searchResult.NextPageOffset
}
while($searchResult.MoreAvailable -eq $True)
# Clear date properties
ClearProperties @($startDateProperty, $endDateProperty)
Action: Delete the User