Quest recently announced that the On Demand Migration (ODM) PowerShell API module is now available in the PowerShell gallery. To help get you started, we have compiled a few example scripts that demonstrate how to perform common migration activities using the ODM API.
The examples below assume you have already connected to ODM and selected your Organization, Project, and ProjectWorkload, as described in the ODM API New Feature Spotlight. Note that these scripts are provided as-is for example purposes only, and you may need to modify them to work for your specific project.
This example demonstrates how to create a task to discover all accounts, start the task and monitor the status of the task until it is no longer In Progress.
# Discover objects in Accounts Project # ######################################### $SleepMinute = 1 # Defines the wait time in minutes between querying the task status $TaskName = "Discover All Objects" # Defines the tasks settings $ACCDiscoverParam = @{Name = $Taskname DiscoverUsingCSV = $false FilePath = $null DiscoverUsingGroups = $false DiscoveryGroupsFilePath = $null CreateApplicationAccessPolicy = $false NotifyOnTaskCompletion = $true NotifyOnlyOnTaskFailure = $false NotificationRecipientEmail = "example@domain.com" ScheduledStartTime = $null } # Create the task $Task = New-OdmDiscoveryTask @ACCDiscoverParam # Run the task Start-OdmTask $Task # Monitor task until no longer In Progress Do { Clear-Host $Running = Get-OdmTask -All -TaskId $Task.Id $Running | Sort-Object Name | Select-Object Name, Progress, Status | Format-Table * If ($Running.Status -eq "In Progress") { Start-Sleep ($SleepMinute * 60) } }While ($Running.Status -eq "In Progress") #########################################
This example demonstrates how to create multiple tasks with the defined number of objects. For example, if you need to migrate 2000 accounts, you can split the migration into 10 tasks with 200 accounts each. Note: This example creates the tasks, but does not run them.
# Create task(s) for migrating user accounts # ############################################# # The below script will create tasks based on the setting in NumberOfObjects variable. # Using Type to filter for User objects only. $TaskName = "Migrate User Accounts" $NumberOfObjects = 50 # Defines the number of accounts added to a single task # Download every object from project $AllObjects = Get-OdmObject -All Write-Host "All Objects count:"$AllObjects.Count # Filter for objects where the Type is User $Objects = $AllObjects | Where-Object { $_.Type -eq "User" } Write-Host "Available Objects to create task count:"$Objects.Count $SegmentCount = 1; $SegmentStart = 0 # Calculate how many tasks will be created $WillCreate = [math]::ceiling($Objects.Count / $NumberOfObjects) Do { $Objects2Add = $Objects | Select-Object -Skip $SegmentStart -First $NumberOfObjects $CurrTaskName = ($TaskName + " " + $SegmentCount + " / " + $WillCreate) Write-Host $SegmentCount" / "$WillCreate # Defines the tasks settings $ACCMigParam = @{Name = $CurrTaskName TargetUpnSuffix = $null CustomForwardingDomain = $null SendInvitation = $true InviteRedirectUrl = $null NotifyOnTaskCompletion = $true NotifyOnlyOnTaskFailure = $false NotificationRecipientEmail = "example@domain.com" ScheduledStartTime = $null } # Create task $CurrTask = New-OdmMigrationTask @ACCMigParam # Adding the defined number of accounts to the task $CurrTask | Add-OdmObject -object $Objects2Add $SegmentStart = $SegmentStart + $NumberOfObjects $SegmentCount = $SegmentCount + 1 }While ($Objects.Count -gt $SegmentStart) #########################
This example script demonstrates how to create a task from a collection and schedule the task to start after a defined number of minutes.
# Create task for Migrating mailbox(es) from a Collection # ####################################################### # Create and start a mailbox migration task based on Collection members and schedule the start $CollectionName = "Example" # Defines the name of Collection $TaskName = "MBX Migration of " + $CollectionName + " Collection" $ScheduleFromNowMinutes = 60 # Defines the task start delay from current time # Get collection $Coll = Get-OdmCollection -WildcardFilter @{name = $CollectionName } # Get the mailboxes from collection $Objects = Get-OdmCollection -WildcardFilter @{name = $CollectionName } | Get-OdmObject -All Write-Host "Object(s) in Collection:"$Objects.Count # Options to migrate $MBXMigParam = @{ Name = $TaskName MigrateFromArchive = $false MigrateToArchive = $false MigrateMail = $true MigrateCalendar = $true MigrateContacts = $true MigrateTasks = $true MigrateRules = $true MigrateDelegates = $true MigrateRecoverableItems = $true EnableAutomapping = $false MigrateFolderPermissions = $true MigrateAutoReplyState = $true SkipO365LicenseAssignment = $true # O365LicenseToAssign = $null O365LicenseAssignmentType = "Keep" MigrateMailFrom = $null MigrateMailUntil = $null # ExcludeDeletedItems = $false # ExcludeJunkEmail = $false # ExcludeDrafts = $false # ExcludeConversationHistory = $false # ExcludeSentItems = $false # ExcludeInbox = $false # ExcludeMailFolders = $false # IncludeInbox = $true # IncludeSentItems = $true # IncludeDrafts = $true # IncludeDeletedItems = $true # IncludeMailFolders = $true MailForwarding = "Ignore" SwitchDirection = "SourceToTarget" TargetForwardingDomain = $null AllowFolderMapping = $False InboxMappingFolderName = $null DeletedMappingFolderName = $null ArchiveMappingFolderName = $null SentItemsMappingFolderName = $null MigrateLitigationHoldSettings = $true MigrateToCustomFolder = $false CustomFolderName = "" DisableReporting = $false ResetMigration = $false NotifyOnTaskCompletion = $false NotifyOnlyOnTaskFailure = $false NotificationRecipientEmail = $null # OverrideUsageLocation = $false # UsageLocation = "NL" ScheduledStartTime = (Get-Date).AddMinutes($ScheduleFromNowMinutes) } # Create the task $CurrTask = New-OdmMailMigrationTask @MBXMigParam # Add the mailboxes to the task $CurrTask | Add-OdmObject -object $Objects # Add the task to the collection; otherwise the task is not displayed when the collection is selected and you navigate to Tasks Add-OdmTask -Tasks $CurrTask -To $Coll #######################
This example script demonstrates how to create a migration task for OneDrives sized between 1 GB and 10 GB that have not previously been migrated and schedule the task to start at a specific date and time.
# OneDrive Migration # ####################### # This example creates OneDrive migration task for OneDrives sized between 1 GB and 10 GB which have not been previously migrated # Start the task at specific scheduled time "01/23/2024 1:30 PM" $TaskName = "OD Migration" $MinSizeGB = 1 $MaxSizeGB = 10 $ScheduleTime = "01/23/2024 1:30 PM" # "MM/DD/YYYY HH:MM" - 24 hour, "MM/DD/YYYY HH:MM AM/PM" - 12 hour # Get every item $AllObjects = Get-OdmObject -All Write-Host "All Objects count:"$AllObjects.Count # Filter for New OneDrives only $NewObjects = $AllObjects | Where-Object { $_.OneDriveMigrationState -eq "Discovered" } # Discovered the status for New OneDrives Write-Host "New OneDrives count:"$NewObjects.Count # Filter minimum size $MinObjects = $NewObjects | Where-Object { $_.OneDriveSourceTotalSize -gt ($MinSizeGB * 1024 * 1024 * 1024) } Write-Host ("OneDrives above " + $MinSizeGB + " GB count: " + $MinObjects.Count) # Filter maximum size $MaxObjects = $MinObjects | Where-Object { $_.OneDriveSourceTotalSize -lt ($MaxSizeGB * 1024 * 1024 * 1024) } Write-Host ("OneDrives between " + $MinSizeGB + " GB and " + $MaxSizeGB + " GB count: " + $MaxObjects.Count) $Objects = $MaxObjects # Migration task options $ODMigParam = @{ Name = $TaskName MigrationAction = "Skip" # KeepBoth Overwrite DestinationFolderPath = $null TaskPriority = "High" # Highest Low Lowest Medium FileVersions = "All" # Latest LatestAndPrevious MoreVersions # FileVersionsMaxTotalCount = 0 # FileVersionsPriority = "Latest" # LatestPerDay FileVersionMaxSize = 512 Author = "TargetAccount" # SystemAccount PermissionBehaviour = "All" # MigratedContentOnly NoPermission PermissionsInRerun = $True MigrateLinkPermission = $True O365LicenseToAssign = $False O365LicenseAssignmentType = "Keep" # Replace FilterItems = "AllItems" # ItemsToExclude ItemsToInclude # ExcludeFolders = $null # ExcludeFileTypes = $null # ExcludeCreatedBefore = $null # ExcludeModifiedAfter = $null # ExcludeBiggerThan = $null # IncludeFolders = $null # IncludeFileTypes = $null # IncludeCreatedBefore = $null # IncludeModifiedAfter = $null # IncludeBiggerThan = $null RerunIfMissingItems = $True EnableReporting = $True # OverrideUsageLocation = $False # UsageLocation = "NL" ScheduledStartTime = $ScheduleTime } # Create task $CurrTask = New-OdmOneDriveMigrationTask @ODMigParam # Add OneDrives to task $CurrTask | Add-OdmObject -object $Objects #######################
This example script demonstrates how to monitor running tasks and return detailed info, utilizing filters such as Wildcardfilter, tasktype filters, and Get-OdmEvent filters.
# Monitor In Progress tasks # ############################# # Define the task type to monitor or set to ALL to monitor every In Progress task $TaskType = "All" # use ALL to monitor every running task, Types for example, "Mail Migration", "OneDrive Migration" $SleepMinute = 5 # Wait time in minutes before getting task info as it’s looped Do { # Check the tasks to monitor If ($TaskType -eq "All") { $MonTasks = Get-OdmTask -All | Where-Object { $_.Status -eq "In Progress" } } else { $MonTasks = Get-OdmTask -All -Type $TaskType | Where-Object { $_.Status -eq "In Progress" } } # Check if any running tasks; if not, exit If ($MonTasks.Count -lt 1) { Write-Host "No In Progress tasks to monitor" Break } # Get details of each running task Foreach ($MTask in $MonTasks) { # Searching for the first task event, because the task created time and the task real start can be different if the task is scheduled # You can use Wildcardfilter for a quick search in events on server side # Message and details (lower case) are available for Get-OdmEvent, see the sample below $FirstEvent = Get-OdmEvent -All -Filterobject $MTask -WildcardFilter @{message = "Task has been started (*" } | Sort-Object Timestamp | Select-Object -Last 1 # Download the first 1000 errors (by not using -All switch) of the task, using Severity filter # When not using -All switch, only the first 1000 results are returned $Errors = Get-OdmEvent -Filterobject $MTask -Severity Error # If 1000 errors were downloaded, add =< to mark that the count is at least 1000 If ($Errors.Count -eq 1000) { $Erroutput = [string]$Errors.Count $Erroutput = $Erroutput + " =<" } Else { $Erroutput = [string]$Errors.Count } # Download the first 1000 warnings (by not using -All switch) of the task, using Severity filter # When not using -All switch, only the first 1000 results are returned $Warnings = Get-OdmEvent -Filterobject $MTask -Severity Warning # If 1000 warnings were downloaded, add =< to mark that the count is at least 1000 If ($Warnings.Count -eq 1000) { $Warnoutput = [string]$Warnings.Count $Warnoutput = $Warnoutput + " =<" } Else { $Warnoutput = [string]$Warnings.Count } # Calculating how long the task ran, based on when the Task actually started # Need to convert to UTC time, because every timestamp returns in UTC $TaskDurHours = ((New-TimeSpan -Start $FirstEvent.Timestamp -End (Get-Date).ToUniversalTime()).TotalHours).ToString("N2") # Adding the collected info to the task properties Add-Member -InputObject $MTask -MemberType NoteProperty -Name TaskStartUTC -Value $FirstEvent.Timestamp -Force Add-Member -InputObject $MTask -MemberType NoteProperty -Name TaskDurHours -Value $TaskDurHours -Force Add-Member -InputObject $MTask -MemberType NoteProperty -Name ErrInTask -Value $Erroutput -Force Add-Member -InputObject $MTask -MemberType NoteProperty -Name WarnInTask -Value $Warnoutput -Force } # Display the taskinformation Clear-Host $MonTasks | Sort-Object Name | Select-Object Id, Name, Type, Progress, LastResult, Created, TaskStartUTC, TaskDurHours, ErrInTask, WarnInTask | Format-Table * # Inform when last query ran Write-Host ("Last query ran at: " + (Get-Date)) # Wait before getting next task info Write-Host ("Sleeping for " + $SleepMinute + " Minutes") Start-Sleep ($SleepMinute * 60) }While ($True) ####################################
This example script demonstrates how to automatically start tasks based on how many parallel tasks you want to run for a specific task type.
# Automatically Start New tasks # ################################# # When New tasks are created (without scheduled start time), this example will start them based on the variables defined # For example, you created multiple tasks for mailbox migration and wish to run 10 tasks in parallel $TaskType2Run = "Mail Migration" # Types for example, "Mail Migration", "OneDrive Migration" $MaxRunningTasks = 10 $SleepMinute = 5 # Wait time before getting task info as it's looped Do { # Get the current running task based on the tasktype $TotalRunnning = Get-OdmTask -All -Type $TaskType2Run | Where-Object { $_.Status -eq "In Progress" } # Get New tasks based on the tasktype and sort by creation date $QueuedTask = Get-OdmTask -All -Type $TaskType2Run | Where-Object { $_.Status -eq “New” } | Sort-Object Created # If no more New tasks, then exit If ($QueuedTask.Count -eq 0) { Break } # Display In Progress task info Clear-Host $TotalRunnning | Sort-Object Name | Select-Object Id, Name, Type, Progress, LastResult | Format-Table * # Start next task based on MaxRunningTasks variable If ($TotalRunnning.Count -lt $MaxRunningTasks) { Start-OdmTask $QueuedTask[0] Write-Host "Started task:"($QueuedTask[0].Name) } # Display info Write-Host ("Running tasks count: " + $TotalRunnning.Count) Write-Host ("Queued tasks count: " + $QueuedTask.Count) # Inform when last query ran Write-Host ("Last query ran at: " + (Get-Date)) # Wait before getting the next task info Write-Host ("Sleeping for " + $SleepMinute + " Minutes") Start-Sleep ($SleepMinute * 60) }While ($true) ##################################
Further Information
For more information on this and many other features within On Demand Migration, check out the ODM User Guide Appendix A: Using PowerShell. We welcome your feedback and suggestions below and invite you to visit us at Quest.com.
Hi, when trying to create a OneDrive job with New-OdmOneDriveMigrationTask I get an error. I have a custom RBAC role defined. This all works fine for similar Mailbox task creation
The task did not created. HTTP Error code: Forbidden
Description: {"message":"Access is denied. The 'write' permission is required to access the 'tasks' entity.","data":null}
At C:\Users\neil.adams4\OneDrive - National Grid\Documents\WindowsPowerShell\Modules\OdmApi\2.0.123\OdmAPI.WriteFunctions.ps1:114 char:12
+ catch {throw $($($MessagesDictionary['EntityNotCreated'] -f $Enti ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : OperationStopped: (The task did no....","data":null}:String) [], RuntimeException
+ FullyQualifiedErrorId : The task did not created. HTTP Error code: Forbidden
Description: {"message":"Access is denied. The 'write' permission is required to access the 'tasks' entity.","data":null}
Hello Neil,
What is the customer RBAC settings? Let me try to reproduce the issue.
Thanks Jozsef!
Interestingly, when I am added to the Migration Administrator role, the command runs fine. However, only other Migration Administrators can see the resultant task, or view the generated events. The taks is not shown for other people and even sharing a link to the events generated by the task gives an Access Denied message
RBAC permissions
On Demand Organization:
Migration:
Thanks for these great examples! Are there any plans to add the ability to use this OdmApi module with Quest ODM for Active Directory (or any way to use the BinaryTree module still)?
Hi Sam, yes we plan to add API support for ODMAD workloads in the future, but we do not have an exact timeline for it just yet.
Hello, we are trying to run New-OdmAddressRewritingTask but we're not able to do it because we always get:
Incorrect workload type.
At C:\Program Files\WindowsPowerShell\Modules\OdmApi\2.1.30\OdmAPI.CommonFunctions.ps1:172 char:17
+ throw $MessagesDictionary['IncorrectWorkLoadType']
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : OperationStopped: (Incorrect workload type.:String) [], RuntimeException
+ FullyQualifiedErrorId : Incorrect workload type.
When we run:
$task = New-OdmAddressRewritingTask -Name "Address Rewriting Task (From PowerShell)" -Action Deploy
It doesn't matter what Action we set, always same error, can you please help?
Hello Asola,
This is a feature of Domain Coexistence, do you have license for it? ODMAD Domain Rewrite has no yet Powershell support.
If it is Domain Coexistence:
How did you select Account project workload?
The format is like:
Get-OdmProject -ProjectId xxxxxxxx| Get-OdmProjectWorkload -Type Accounts | Select-OdmProjectWorkload