{"id":43802,"date":"2026-04-07T14:58:14","date_gmt":"2026-04-07T12:58:14","guid":{"rendered":"https:\/\/www.dbi-services.com\/blog\/?p=43802"},"modified":"2026-04-07T14:58:16","modified_gmt":"2026-04-07T12:58:16","slug":"scaling-ssrs-migrations-multi-threaded-automation-for-pbirs-2025","status":"publish","type":"post","link":"https:\/\/www.dbi-services.com\/blog\/scaling-ssrs-migrations-multi-threaded-automation-for-pbirs-2025\/","title":{"rendered":"Scaling SSRS Migrations: Multi-Threaded Automation for PBIRS 2025"},"content":{"rendered":"\n<p>Modernizing a reporting platform is a pivotal milestone for any BI infrastructure. Whether it\u2019s a standard upgrade or a forced transition to <strong><a href=\"https:\/\/learn.microsoft.com\/en-us\/power-bi\/report-server\/download-powerbi-report-server\">Power BI Report Server (PBIRS)<\/a><\/strong> following the decommissioning of SSRS in SQL Server 2025, the operation is critical. For the purposes of our lab, we will use an SSRS 2017 source, but the logic remains universal: regardless of the original version, the goal is to ensure the continuity of your decision-making services without sacrificing your mental health in the process.<\/p>\n\n\n\n<p>As my colleague Amine Haloui explained in <a href=\"https:\/\/www.dbi-services.com\/blog\/sql-server-2025-retirement-of-sql-server-reporting-services-ssrs\/\" id=\"https:\/\/www.dbi-services.com\/blog\/sql-server-2025-retirement-of-sql-server-reporting-services-ssrs\/\">a recent blog post<\/a>, several strategies exist for migrating an instance. The &#8220;Lift and Shift&#8221; method (restoring the <code>ReportServer<\/code> database onto a new instance) is often the most attractive on paper. However, the reality on the ground can be more temperamental.<\/p>\n\n\n\n<p>In some production environments, the target PBIRS instance already exists, hosts its own content, or follows specific configurations that prohibit simply overwriting its underlying <code>ReportServer<\/code> database. Therefore, we are proceeding here on the premise of a selective and granular migration: we must inject the SSRS catalog into an active PBIRS environment without burning everything to the ground in the process.<\/p>\n\n\n\n<p>When faced with inventories exceeding hundreds or even thousands of reports (RDL), folders, and datasources, a manual approach via the web interface is not an option and automation becomes a necessity.<\/p>\n\n\n\n<p>This article analyzes a systematic approach based on the <code>ReportingServicesTools<\/code> PowerShell module. The objective is to provide a robust methodology to extract your catalog and redeploy it intelligently, while managing the necessary reconfigurations along the way.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-phase-1-smart-dumping-building-the-local-staging-area\">Phase 1: Smart Dumping \u2013 Building the Local Staging Area<\/h2>\n\n\n\n<p>To migrate cleanly, objects must first be isolated. The idea is not to blindly vacuum everything, but to target the critical folders of your SSRS instance and transform them into flat files (.rdl and .rds) within a local staging area. If your SSRS instance contains specific object types, the scripts can easily be adapted to include them as well.<\/p>\n\n\n\n<p>This is where the power of the <strong><a href=\"https:\/\/learn.microsoft.com\/en-us\/powershell\/module\/microsoft.powershell.management\/new-webserviceproxy?view=powershell-5.1\">SOAP Proxy<\/a><\/strong> comes into play. Rather than multiplying slow HTTP calls, we use the native service interface to list and extract our components:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: powershell; title: ; notranslate\" title=\"\">\n$sourceUrl  = &quot;http:\/\/your-ssrs-server\/ReportServer&quot;\n$exportRoot = &quot;H:\\Migration_Dump&quot;\n\n$proxySource = New-RsWebServiceProxy -ReportServerUri $sourceUrl\n<\/pre><\/div>\n\n\n<p>In a production environment, SSRS folders are often a messy mix of reports, data sources, images, and sometimes obsolete semantic models. To maintain total control over what we export, we isolate the filtering logic.<\/p>\n\n\n\n<p>This <code>Get-AllItemsByType<\/code> function allows us to retrieve only what truly matters to us, based on the <strong>TypeName<\/strong> and <strong>file extension<\/strong> returned by the API.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: powershell; title: ; notranslate\" title=\"\">\nfunction Get-AllItemsByType {\n    param(\n        &#x5B;string]$CurrentPath,\n        $Proxy,\n        &#x5B;string]$TypeName \n    )\n    try {\n        return $Proxy.ListChildren($CurrentPath, $true) | Where-Object { $_.TypeName -eq $TypeName }\n    } catch {\n        Write-Host &quot;  &#x5B;!] Error on $CurrentPath : $($_.Exception.Message)&quot; -ForegroundColor Red\n        return $null\n    }\n}\n<\/pre><\/div>\n\n\n<p>This mapping between the file type and its extension must be defined upfront in a dictionary:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: powershell; title: ; notranslate\" title=\"\">\n$extensionMap = @{\n    &quot;Report&quot; = &quot;.rdl&quot;\n    &quot;DataSource&quot; = &quot;.rds&quot;\n}\n<\/pre><\/div>\n\n\n<p>A crucial point in extracting SSRS objects is preserving their context. To ensure a seamless import into PBIRS 2025, we must recreate the exact folder hierarchy of the source server locally.<\/p>\n\n\n\n<p>The trick lies in transforming the SSRS path (formatted as <code>\/Folder\/SubFolder\/Report<\/code>) into a valid Windows path, while simultaneously handling the extension mapping (<code>.rdl<\/code> for reports, <code>.rds<\/code> for DataSources).<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: powershell; title: ; notranslate\" title=\"\">\nfunction Export-SsrsItems {\n    param(\n        &#x5B;string]$RootPath,\n        $Proxy,\n        &#x5B;string]$TypeName,\n        &#x5B;string]$ExportRoot\n    )\n\n    $items = Get-AllItemsByType -CurrentPath $RootPath -Proxy $Proxy -TypeName $TypeName\n\n    foreach ($item in $items) {\n        $relativeItemPath = $item.Path.TrimStart(&#039;\/&#039;).Replace(&quot;\/&quot;, &quot;\\&quot;)\n        $localFilePath    = Join-Path $ExportRoot $relativeItemPath\n        $localDirectory   = Split-Path -Path $localFilePath -Parent\n\n        if (-not (Test-Path $localDirectory)) {\n            New-Item -ItemType Directory -Path $localDirectory -Force | Out-Null\n        }\n\n        Out-RsCatalogItem -Path $item.Path -Destination $localDirectory -Proxy $Proxy\n    }\n}\n<\/pre><\/div>\n\n\n<p>By doing this, your <code>H:\\Migration_Dump<\/code> becomes the exact mirror of your SSRS portal. This structural rigor is what will allow us, in the next step, to remap our data sources without having to hunt down which report belongs to which department.<\/p>\n\n\n\n<div class=\"wp-block-columns no-bottom-margin is-layout-flex wp-container-core-columns-is-layout-9d6595d7 wp-block-columns-is-layout-flex\">\n<div class=\"wp-block-column is-layout-flow wp-block-column-is-layout-flow\">\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"284\" src=\"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2026\/04\/image-7-1024x284.png\" alt=\"\" class=\"wp-image-43818\" srcset=\"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2026\/04\/image-7-1024x284.png 1024w, https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2026\/04\/image-7-300x83.png 300w, https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2026\/04\/image-7-768x213.png 768w, https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2026\/04\/image-7.png 1145w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n<\/div>\n\n\n\n<div class=\"wp-block-column is-layout-flow wp-block-column-is-layout-flow\">\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"887\" height=\"255\" src=\"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2026\/04\/image-6.png\" alt=\"\" class=\"wp-image-43817\" srcset=\"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2026\/04\/image-6.png 887w, https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2026\/04\/image-6-300x86.png 300w, https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2026\/04\/image-6-768x221.png 768w\" sizes=\"auto, (max-width: 887px) 100vw, 887px\" \/><\/figure>\n<\/div>\n<\/div>\n\n\n\n<p>Finally, we define the folders we wish to export along with the document types they contain (since a migration is often the perfect time for a bit of spring cleaning):<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: powershell; title: ; notranslate\" title=\"\">\n$exportTasks = @(\n    @{ Path = &quot;\/Migration_Source_2&quot;; Types = @(&quot;Report&quot;) },\n    @{ Path = &quot;\/Data Sources&quot;;  Types = @(&quot;DataSource&quot;) }\n)\n\nWrite-Host &quot;--- Selective Export Started ---&quot; -ForegroundColor Cyan\n\nforeach ($task in $exportTasks) {\n    foreach ($typeName in $task.Types) {\n        $ext = $extensionMap&#x5B;$typeName]\n        Export-SsrsItems `\n            -RootPath   $task.Path `\n            -Proxy      $proxySource `\n            -TypeName   $typeName `\n            -Extension  $ext `\n            -ExportRoot $exportRoot\n    }\n}\n<\/pre><\/div>\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"682\" height=\"385\" src=\"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2026\/04\/image-1.png\" alt=\"\" class=\"wp-image-43807\" srcset=\"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2026\/04\/image-1.png 682w, https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2026\/04\/image-1-300x169.png 300w\" sizes=\"auto, (max-width: 682px) 100vw, 682px\" \/><\/figure>\n<\/div>\n\n\n<h2 class=\"wp-block-heading\" id=\"h-phase-2-data-source-patching-mass-xml-transformation\">Phase 2: Data Source Patching \u2013 Mass XML Transformation<\/h2>\n\n\n\n<p>Once the extraction is complete, you have a local mirror of your source instance, but the data sources still point to the legacy infrastructure.<\/p>\n\n\n\n<p>Instead of manually fixing each connection after the import (the best way to miss half of them), we will apply an automated transformation directly to our local XML files. This allows us to update connection strings in bulk before a single report even hits the target server.<\/p>\n\n\n\n<p>The idea is simple: use PowerShell to inject the new SQL instance wherever necessary, ensuring a functional deployment from the very first second:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: powershell; title: ; notranslate\" title=\"\">\n$allDataSources = Get-ChildItem -Path $exportRoot -Filter &quot;*.rds&quot; -Recurse\n\nWrite-Host &quot;&#x5B;&gt;] Datasources updated in : $exportRoot&quot; -ForegroundColor Yellow\n\nforeach ($dsFile in $allDataSources) {\n    &#x5B;xml]$xmlContent = Get-Content $dsFile.FullName\n \n    $node = $xmlContent.SelectSingleNode(&quot;\/\/ConnectString&quot;)\n    \n    if ($null -ne $node) {\n        $oldValue = $node.&quot;#text&quot; \n        if ($null -eq $oldValue) { $oldValue = $node.InnerText }\n\n        $newValue = $oldValue -replace &quot;OLD_REPORTING_INSTANCE&quot;, &quot;NEW_REPORTING_INSTANCE&quot;\n        \n        if ($oldValue -ne $newValue) {\n            $node.InnerText = $newValue\n            $xmlContent.Save($dsFile.FullName)\n            Write-Host &quot;  &#x5B;v] ConnectString updated in : $($dsFile.Name)&quot; -ForegroundColor Green\n        }\n    } else {\n        Write-Host &quot;  &#x5B;!] ConnectString not found in file $($dsFile.Name)&quot; -ForegroundColor Red\n    }\n}\n<\/pre><\/div>\n\n\n<p>Moreover, since we are interacting directly with the file\u2019s XML structure, this logic isn&#8217;t limited to connection strings: you can apply the same principle to automate changes for any XML property, from timeouts to provider names.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-phase-3-mass-deployment-rebuilding-the-reporting-portal\">Phase 3: Mass Deployment \u2013 Rebuilding the Reporting Portal<\/h2>\n\n\n\n<p>At this stage, the operation is purely mechanical. We once again leverage the <strong>ReportingServicesTools<\/strong> module to recreate the folder structure and upload the <code>.rds<\/code> and <code>.rdl<\/code> files. By following this specific order, PBIRS will automatically restore the links between your reports and their newly patched data sources.<\/p>\n\n\n\n<p>It is worth noting that the script allows for importing into a specific root folder (defined by the <code>$destroot<\/code> variable). This is particularly useful if you want to isolate the migrated assets into a dedicated directory, such as <code>SSRS_Folder<\/code> to keep them distinct from the existing hierarchy. Furthermore, this script is designed with safety in mind: it cannot overwrite or delete anything. If a report with the same name already exists in the same location, the <code>-Overwrite:$false<\/code> argument prevents replacement, ensuring that the import process never destroys existing content.<\/p>\n\n\n\n<p>Here is the final block to complete your migration:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: powershell; title: ; notranslate\" title=\"\">\n$destUrl   = &quot;http:\/\/your-pbirs-server\/ReportServer&quot; \n$localDump = &quot;H:\\Migration_Dump&quot;\n$destRoot  = &quot;\/&quot; #Start import in the root folder\n$proxyDest = New-RsWebServiceProxy -ReportServerUri $destUrl\n\n$extensionMap = @{\n    &quot;Report&quot;     = &quot;.rdl&quot;\n    &quot;DataSource&quot; = &quot;.rds&quot;\n}\n\nfunction Ensure-RsFolderBruteForce {\n    param($fullFolderPath, $Proxy)\n    $parts = $fullFolderPath.Split(&#039;\/&#039;) | Where-Object { $_ -ne &#039;&#039; }\n    $currentPath = &#039;&#039;\n    \n    foreach ($part in $parts) {\n        $parent = if ($currentPath -eq &#039;&#039;) { &quot;\/&quot; } else { $currentPath }\n        $target = if ($currentPath -eq &#039;&#039;) { &quot;\/$part&quot; } else { &quot;$currentPath\/$part&quot; }\n        \n        try {\n            $Proxy.CreateFolder($part, $parent, $null) | Out-Null\n            Write-Host &quot;  &#x5B;DIR] Created : $target&quot; -ForegroundColor Cyan\n        } catch {\n            if ($_.Exception.Message -match &quot;AlreadyExists&quot;) {\n                # Folder already exists but we continue\n            } else {\n                Write-Host &quot;  &#x5B;!] Error for folder $target : $($_.Exception.Message)&quot; -ForegroundColor Red\n            }\n        }\n        $currentPath = $target\n    }\n}\n\nfunction Import-SsrsItem {\n    param(\n        &#x5B;System.IO.FileInfo]$File,\n        &#x5B;string]$LocalDump,\n        &#x5B;string]$DestRoot,\n        $Proxy\n    )\n\n    $relativeDir       = $File.DirectoryName.Replace($LocalDump, &#039;&#039;).Replace(&quot;\\&quot;, &quot;\/&quot;)\n    $targetFolderPath  = ($DestRoot + $relativeDir).Replace(&quot;\/\/&quot;, &quot;\/&quot;)\n    $fullItemPath      = ($targetFolderPath + &quot;\/&quot; + $File.BaseName).Replace(&quot;\/\/&quot;, &quot;\/&quot;)\n\n    Ensure-RsFolderBruteForce -fullFolderPath $targetFolderPath -Proxy $Proxy\n\n    try {\n        Write-RsCatalogItem -Path $File.FullName -Destination $targetFolderPath -Proxy $Proxy -Overwrite:$false\n        Write-Host &quot;  &#x5B;DONE] Imported: $fullItemPath&quot; -ForegroundColor Green\n    }\n    catch {\n        if ($_.Exception.Message -match &quot;already exists&quot;) {\n            Write-Host &quot;  &#x5B;SKIP] Already created : $fullItemPath&quot; -ForegroundColor Gray\n        } else {\n            Write-Host &quot;  &#x5B;FAIL] Error $fullItemPath : $($_.Exception.Message)&quot; -ForegroundColor Red\n        }\n    }\n}\n\n$importOrder = @(&quot;DataSource&quot;, &quot;Report&quot;)\n\nforeach ($typeName in $importOrder) {\n    $extension = $extensionMap&#x5B;$typeName]\n    Write-Host &quot;`n&#x5B;PASS] Import of object with type : $typeName ($extension)&quot; -ForegroundColor Magenta\n    \n    $filesToImport = Get-ChildItem -Path $localDump -Filter &quot;*$extension&quot; -Recurse\n\n    if ($filesToImport.Count -eq 0) {\n        Write-Host &quot;  &#x5B;i] No file with $extension found.&quot; -ForegroundColor Gray\n        continue\n    }\n\n    foreach ($file in $filesToImport) {\n        Import-SsrsItem -File $file -LocalDump $localDump -DestRoot $destRoot -Proxy $proxyDest\n    }\n}\n\nWrite-Host &quot;`nImport done!&quot; -ForegroundColor Green\n<\/pre><\/div>\n\n\n<p>Importing via SOAP is more resource-intensive than extraction, as the server must validate every piece of metadata and physically recreate the path for each report. On large volumes, this stage can become a bottleneck (averaging ~1 second per report).<\/p>\n\n\n\n<p>To overcome this, we can parallelize the import by folder, creating multiple background jobs running on separate threads. Here is the general skeleton to implement this multi-threaded approach:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: powershell; title: ; notranslate\" title=\"\">\n$maxJobs = 5 \n\nforeach ($file in $filesToImport) {\n    while ((Get-Job -State Running).Count -ge $maxJobs) {\n        Start-Sleep -Milliseconds 500\n    }\n\n    Start-Job -Name &quot;Import_$($file.Name)&quot; -ScriptBlock {\n        param($f, $url, $targetPath)\n\n        $Proxy = New-RsWebServiceProxy -ReportServerUri $url\n        \n        try {\n            Write-RsCatalogItem -Path $f.FullName -Destination $targetPath -Proxy $Proxy -Overwrite:$false\n            return &quot;SUCCESS: $($f.Name)&quot;\n        } catch {\n            return &quot;ERROR: $($f.Name) -&gt; $($_.Exception.Message)&quot;\n        }\n    } -ArgumentList $file, $destUrl, $targetFolderPath\n}\n<\/pre><\/div>\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full is-resized\"><img loading=\"lazy\" decoding=\"async\" width=\"520\" height=\"330\" src=\"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2026\/04\/image.png\" alt=\"\" class=\"wp-image-43806\" style=\"width:522px;height:auto\" srcset=\"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2026\/04\/image.png 520w, https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2026\/04\/image-300x190.png 300w\" sizes=\"auto, (max-width: 520px) 100vw, 520px\" \/><\/figure>\n<\/div>\n\n\n<p><strong>Note :<\/strong> The <code><a href=\"https:\/\/www.powershelladmin.com\/wiki\/PowerShell_foreach_loops_and_ForEach-Object.php\" id=\"https:\/\/www.powershelladmin.com\/wiki\/PowerShell_foreach_loops_and_ForEach-Object.php\">-Parallel<\/a><\/code> parameter is a feature of the <code>ForEach-Object<\/code> cmdlet introduced in PowerShell 7 to enable native multi-threading. While it allows for processing multiple objects simultaneously, it is not reliably supported by the <code>ReportingServicesTools<\/code> library as the underlying API is not thread-safe. To ensure stability and avoid session collisions, it is recommended to use the <code>Start-Job<\/code> method instead, as it provides better process isolation for each task.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-key-takeaways-for-a-seamless-cutover\">Key Takeaways for a Seamless Cutover<\/h2>\n\n\n\n<p>Migrating to Power BI Report Server shouldn&#8217;t be a manual challenge. By adopting this <strong>PowerShell-driven ETL approach<\/strong>, you replace the uncertainty of manual intervention with industrial-grade rigor.<\/p>\n\n\n\n<p>The primary advantage lies in consistency: regardless of the report volume or folder complexity, the script guarantees an identical and predictable result every single time. By isolating extraction, XML transformation, and ordered importation, you maintain total control over your data integrity.<\/p>\n\n\n\n<p>Ultimately, automation is about securing your delivery and freeing up time for what truly matters: leveraging your data on your brand-new PBIRS 2025 platform.<\/p>\n\n\n\n<p>Happy migrating!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Modernizing a reporting platform is a pivotal milestone for any BI infrastructure. Whether it\u2019s a standard upgrade or a forced transition to Power BI Report Server (PBIRS) following the decommissioning of SSRS in SQL Server 2025, the operation is critical. For the purposes of our lab, we will use an SSRS 2017 source, but the [&hellip;]<\/p>\n","protected":false},"author":157,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[294,99],"tags":[3974,272,1346,596],"type_dbi":[2874],"class_list":["post-43802","post","type-post","status-publish","format-standard","hentry","category-business-intelligence","category-sql-server","tag-pbirs","tag-powershell","tag-sqlserver","tag-ssrs","type-sql-server"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v27.2 (Yoast SEO v27.2) - https:\/\/yoast.com\/product\/yoast-seo-premium-wordpress\/ -->\n<title>Scaling SSRS Migrations: Multi-Threaded Automation for PBIRS 2025 - dbi Blog<\/title>\n<meta name=\"description\" content=\"Gemini a ditMigrate SSRS to PBIRS 2025: a PowerShell ETL to automate extraction, XML patching, and parallelized deployment.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.dbi-services.com\/blog\/scaling-ssrs-migrations-multi-threaded-automation-for-pbirs-2025\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Scaling SSRS Migrations: Multi-Threaded Automation for PBIRS 2025\" \/>\n<meta property=\"og:description\" content=\"Gemini a ditMigrate SSRS to PBIRS 2025: a PowerShell ETL to automate extraction, XML patching, and parallelized deployment.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.dbi-services.com\/blog\/scaling-ssrs-migrations-multi-threaded-automation-for-pbirs-2025\/\" \/>\n<meta property=\"og:site_name\" content=\"dbi Blog\" \/>\n<meta property=\"article:published_time\" content=\"2026-04-07T12:58:14+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2026-04-07T12:58:16+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2026\/04\/image-7.png\" \/>\n\t<meta property=\"og:image:width\" content=\"1145\" \/>\n\t<meta property=\"og:image:height\" content=\"318\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"Louis Tochon\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Louis Tochon\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"6 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/www.dbi-services.com\/blog\/scaling-ssrs-migrations-multi-threaded-automation-for-pbirs-2025\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/www.dbi-services.com\/blog\/scaling-ssrs-migrations-multi-threaded-automation-for-pbirs-2025\/\"},\"author\":{\"name\":\"Louis Tochon\",\"@id\":\"https:\/\/www.dbi-services.com\/blog\/#\/schema\/person\/e4195b0cb120295b3407a502c23e75b6\"},\"headline\":\"Scaling SSRS Migrations: Multi-Threaded Automation for PBIRS 2025\",\"datePublished\":\"2026-04-07T12:58:14+00:00\",\"dateModified\":\"2026-04-07T12:58:16+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/www.dbi-services.com\/blog\/scaling-ssrs-migrations-multi-threaded-automation-for-pbirs-2025\/\"},\"wordCount\":1070,\"commentCount\":0,\"image\":{\"@id\":\"https:\/\/www.dbi-services.com\/blog\/scaling-ssrs-migrations-multi-threaded-automation-for-pbirs-2025\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2026\/04\/image-7-1024x284.png\",\"keywords\":[\"PBIRS\",\"PowerShell\",\"SQLServer\",\"SSRS\"],\"articleSection\":[\"Business Intelligence\",\"SQL Server\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/www.dbi-services.com\/blog\/scaling-ssrs-migrations-multi-threaded-automation-for-pbirs-2025\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/www.dbi-services.com\/blog\/scaling-ssrs-migrations-multi-threaded-automation-for-pbirs-2025\/\",\"url\":\"https:\/\/www.dbi-services.com\/blog\/scaling-ssrs-migrations-multi-threaded-automation-for-pbirs-2025\/\",\"name\":\"Scaling SSRS Migrations: Multi-Threaded Automation for PBIRS 2025 - dbi Blog\",\"isPartOf\":{\"@id\":\"https:\/\/www.dbi-services.com\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/www.dbi-services.com\/blog\/scaling-ssrs-migrations-multi-threaded-automation-for-pbirs-2025\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/www.dbi-services.com\/blog\/scaling-ssrs-migrations-multi-threaded-automation-for-pbirs-2025\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2026\/04\/image-7-1024x284.png\",\"datePublished\":\"2026-04-07T12:58:14+00:00\",\"dateModified\":\"2026-04-07T12:58:16+00:00\",\"author\":{\"@id\":\"https:\/\/www.dbi-services.com\/blog\/#\/schema\/person\/e4195b0cb120295b3407a502c23e75b6\"},\"description\":\"Gemini a ditMigrate SSRS to PBIRS 2025: a PowerShell ETL to automate extraction, XML patching, and parallelized deployment.\",\"breadcrumb\":{\"@id\":\"https:\/\/www.dbi-services.com\/blog\/scaling-ssrs-migrations-multi-threaded-automation-for-pbirs-2025\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/www.dbi-services.com\/blog\/scaling-ssrs-migrations-multi-threaded-automation-for-pbirs-2025\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.dbi-services.com\/blog\/scaling-ssrs-migrations-multi-threaded-automation-for-pbirs-2025\/#primaryimage\",\"url\":\"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2026\/04\/image-7.png\",\"contentUrl\":\"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2026\/04\/image-7.png\",\"width\":1145,\"height\":318},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/www.dbi-services.com\/blog\/scaling-ssrs-migrations-multi-threaded-automation-for-pbirs-2025\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Accueil\",\"item\":\"https:\/\/www.dbi-services.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Scaling SSRS Migrations: Multi-Threaded Automation for PBIRS 2025\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/www.dbi-services.com\/blog\/#website\",\"url\":\"https:\/\/www.dbi-services.com\/blog\/\",\"name\":\"dbi Blog\",\"description\":\"\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/www.dbi-services.com\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Person\",\"@id\":\"https:\/\/www.dbi-services.com\/blog\/#\/schema\/person\/e4195b0cb120295b3407a502c23e75b6\",\"name\":\"Louis Tochon\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/secure.gravatar.com\/avatar\/ce0ee48c64e763e6c4076e21c80729d15bc4493288aeb8695125c69082100e10?s=96&d=mm&r=g\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/ce0ee48c64e763e6c4076e21c80729d15bc4493288aeb8695125c69082100e10?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/ce0ee48c64e763e6c4076e21c80729d15bc4493288aeb8695125c69082100e10?s=96&d=mm&r=g\",\"caption\":\"Louis Tochon\"},\"url\":\"https:\/\/www.dbi-services.com\/blog\/author\/louistochon\/\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Scaling SSRS Migrations: Multi-Threaded Automation for PBIRS 2025 - dbi Blog","description":"Gemini a ditMigrate SSRS to PBIRS 2025: a PowerShell ETL to automate extraction, XML patching, and parallelized deployment.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.dbi-services.com\/blog\/scaling-ssrs-migrations-multi-threaded-automation-for-pbirs-2025\/","og_locale":"en_US","og_type":"article","og_title":"Scaling SSRS Migrations: Multi-Threaded Automation for PBIRS 2025","og_description":"Gemini a ditMigrate SSRS to PBIRS 2025: a PowerShell ETL to automate extraction, XML patching, and parallelized deployment.","og_url":"https:\/\/www.dbi-services.com\/blog\/scaling-ssrs-migrations-multi-threaded-automation-for-pbirs-2025\/","og_site_name":"dbi Blog","article_published_time":"2026-04-07T12:58:14+00:00","article_modified_time":"2026-04-07T12:58:16+00:00","og_image":[{"width":1145,"height":318,"url":"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2026\/04\/image-7.png","type":"image\/png"}],"author":"Louis Tochon","twitter_card":"summary_large_image","twitter_misc":{"Written by":"Louis Tochon","Est. reading time":"6 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.dbi-services.com\/blog\/scaling-ssrs-migrations-multi-threaded-automation-for-pbirs-2025\/#article","isPartOf":{"@id":"https:\/\/www.dbi-services.com\/blog\/scaling-ssrs-migrations-multi-threaded-automation-for-pbirs-2025\/"},"author":{"name":"Louis Tochon","@id":"https:\/\/www.dbi-services.com\/blog\/#\/schema\/person\/e4195b0cb120295b3407a502c23e75b6"},"headline":"Scaling SSRS Migrations: Multi-Threaded Automation for PBIRS 2025","datePublished":"2026-04-07T12:58:14+00:00","dateModified":"2026-04-07T12:58:16+00:00","mainEntityOfPage":{"@id":"https:\/\/www.dbi-services.com\/blog\/scaling-ssrs-migrations-multi-threaded-automation-for-pbirs-2025\/"},"wordCount":1070,"commentCount":0,"image":{"@id":"https:\/\/www.dbi-services.com\/blog\/scaling-ssrs-migrations-multi-threaded-automation-for-pbirs-2025\/#primaryimage"},"thumbnailUrl":"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2026\/04\/image-7-1024x284.png","keywords":["PBIRS","PowerShell","SQLServer","SSRS"],"articleSection":["Business Intelligence","SQL Server"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.dbi-services.com\/blog\/scaling-ssrs-migrations-multi-threaded-automation-for-pbirs-2025\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.dbi-services.com\/blog\/scaling-ssrs-migrations-multi-threaded-automation-for-pbirs-2025\/","url":"https:\/\/www.dbi-services.com\/blog\/scaling-ssrs-migrations-multi-threaded-automation-for-pbirs-2025\/","name":"Scaling SSRS Migrations: Multi-Threaded Automation for PBIRS 2025 - dbi Blog","isPartOf":{"@id":"https:\/\/www.dbi-services.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.dbi-services.com\/blog\/scaling-ssrs-migrations-multi-threaded-automation-for-pbirs-2025\/#primaryimage"},"image":{"@id":"https:\/\/www.dbi-services.com\/blog\/scaling-ssrs-migrations-multi-threaded-automation-for-pbirs-2025\/#primaryimage"},"thumbnailUrl":"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2026\/04\/image-7-1024x284.png","datePublished":"2026-04-07T12:58:14+00:00","dateModified":"2026-04-07T12:58:16+00:00","author":{"@id":"https:\/\/www.dbi-services.com\/blog\/#\/schema\/person\/e4195b0cb120295b3407a502c23e75b6"},"description":"Gemini a ditMigrate SSRS to PBIRS 2025: a PowerShell ETL to automate extraction, XML patching, and parallelized deployment.","breadcrumb":{"@id":"https:\/\/www.dbi-services.com\/blog\/scaling-ssrs-migrations-multi-threaded-automation-for-pbirs-2025\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.dbi-services.com\/blog\/scaling-ssrs-migrations-multi-threaded-automation-for-pbirs-2025\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.dbi-services.com\/blog\/scaling-ssrs-migrations-multi-threaded-automation-for-pbirs-2025\/#primaryimage","url":"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2026\/04\/image-7.png","contentUrl":"https:\/\/www.dbi-services.com\/blog\/wp-content\/uploads\/sites\/2\/2026\/04\/image-7.png","width":1145,"height":318},{"@type":"BreadcrumbList","@id":"https:\/\/www.dbi-services.com\/blog\/scaling-ssrs-migrations-multi-threaded-automation-for-pbirs-2025\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Accueil","item":"https:\/\/www.dbi-services.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Scaling SSRS Migrations: Multi-Threaded Automation for PBIRS 2025"}]},{"@type":"WebSite","@id":"https:\/\/www.dbi-services.com\/blog\/#website","url":"https:\/\/www.dbi-services.com\/blog\/","name":"dbi Blog","description":"","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.dbi-services.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Person","@id":"https:\/\/www.dbi-services.com\/blog\/#\/schema\/person\/e4195b0cb120295b3407a502c23e75b6","name":"Louis Tochon","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/secure.gravatar.com\/avatar\/ce0ee48c64e763e6c4076e21c80729d15bc4493288aeb8695125c69082100e10?s=96&d=mm&r=g","url":"https:\/\/secure.gravatar.com\/avatar\/ce0ee48c64e763e6c4076e21c80729d15bc4493288aeb8695125c69082100e10?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/ce0ee48c64e763e6c4076e21c80729d15bc4493288aeb8695125c69082100e10?s=96&d=mm&r=g","caption":"Louis Tochon"},"url":"https:\/\/www.dbi-services.com\/blog\/author\/louistochon\/"}]}},"_links":{"self":[{"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/posts\/43802","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/users\/157"}],"replies":[{"embeddable":true,"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/comments?post=43802"}],"version-history":[{"count":41,"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/posts\/43802\/revisions"}],"predecessor-version":[{"id":43852,"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/posts\/43802\/revisions\/43852"}],"wp:attachment":[{"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/media?parent=43802"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/categories?post=43802"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/tags?post=43802"},{"taxonomy":"type","embeddable":true,"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/type_dbi?post=43802"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}