On Demand Migration – PowerShell API – Example Scripts

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.

Discover Accounts - Create task, start task, monitor task status

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 -eq "In Progress") { Start-Sleep ($SleepMinute * 60) }
}While ($Running.Status -eq "In Progress")
#########################################

Migrate Accounts - Create multiple tasks with a defined number of objects

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)
#########################

Migrate Mailboxes - Create task from collection, schedule task start time

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
#######################

Migrate OneDrive - Create task, filter by size, schedule task start time

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
#######################

Monitor Tasks - Return status and details for In Progress tasks

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)
####################################

Auto Start Tasks - Auto-start tasks, limit number of concurrent parallel tasks

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:

    • Can Export Data (Migration Only)

    Migration:

    • View Projects and Manage Selected Services: All Selected
    • View Projects: All Selected
    • Run A full discovery: Accounts, Teams, SharePoint, Public Folders
    • Run a scoped Discovery with CSV file: Accounts, Teams, SharePoint
    • Run Content discovery tasks: Accounts, Mailboxes, OneDrive, SharePoint
    • Run match and map tasks: Accounts, Teams, Sharepoint
    • Run Provision and migration tasks: all
    • Manage Collections: all
    • Update and delete migration objects: all
    • Ack and clear Task events: all
    • Manage DUA: Yes
    • View Migration Reports: All
    • View Reporting dashboard: All