functions/Get-UdeDeveloperFile.ps1
|
<# .SYNOPSIS Gets UDE developer files for a specified environment. .DESCRIPTION This function retrieves UDE developer files for a specified environment. .PARAMETER EnvironmentId The ID of the environment that you want to work against. Supports wildcard patterns. Can be either the environment name or the environment GUID. .PARAMETER Path The path to the directory where the developer files will be saved. Defaults to "C:\Temp\d365bap.tools\UdeDeveloperFiles". .PARAMETER Files The types of developer files to retrieve. Can be one or more of the following values: "All", "SystemMetadata", "FinOpsVsix22", "TraceParser", "CrossReference". Defaults to "All". .PARAMETER Download Instructs the function to download the developer files to the specified path. .PARAMETER ClearSystemPackages Instructs the function to clear the existing PackagesLocalDirectory before extracting the SystemMetadata file. Use with caution as it will delete existing files. Can be useful when the extraction has failed previously and you want to ensure a clean state for the extraction. .EXAMPLE PS C:\> Get-UdeDeveloperFile -EnvironmentId "env-123" This will retrieve the UDE developer files for the specified environment ID without downloading them. .EXAMPLE PS C:\> Get-UdeDeveloperFile -EnvironmentId "env-123" -Download This will download the UDE developer files for the specified environment ID to the default path. .EXAMPLE PS C:\> Get-UdeDeveloperFile -EnvironmentId "env-123" -Download -Files "SystemMetadata","TraceParser" This will download only the SystemMetadata and TraceParser UDE developer files for the specified environment ID to the default path. .EXAMPLE PS C:\> Get-UdeDeveloperFile -EnvironmentId "env-123" -Download -ClearSystemPackages This will download the UDE developer files for the specified environment ID to the default path. It will clear the existing PackagesLocalDirectory before extracting the SystemMetadata file, ensuring a clean state for the extraction. .NOTES Author: Mötz Jensen (@Splaxi) #> function Get-UdeDeveloperFile { [CmdletBinding()] [OutputType('System.Object[]')] param ( [Parameter (Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [Alias("PpacEnvId")] [string] $EnvironmentId, [string] $Path = "C:\Temp\d365bap.tools\UdeDeveloperFiles", [ValidateSet('All', 'SystemMetadata', 'FinOpsVsix22', 'TraceParser', 'CrossReference')] [string[]] $Files = 'All', [switch] $Download, [switch] $ClearSystemPackages ) begin { Add-Type -AssemblyName System.IO.Compression.FileSystem $executable = Get-PSFConfigValue -FullName "d365bap.tools.path.azcopy" $endpoints = @("SystemMetadata", "FinOpsVsix22", "TraceParser", "CrossReference") $colFileTypes = @() if ($Files -eq 'All') { $colFileTypes = $endpoints } else { $colFileTypes = $Files } } process { if (Test-PSFFunctionInterrupt) { return } $envObj = Get-UnifiedEnvironment -EnvironmentId $EnvironmentId | Select-Object -First 1 if ($null -eq $envObj) { $messageString = "Could not find environment with Id <c='em'>$EnvironmentId</c>. Please verify the Id and try again, or list available environments using <c='em'>Get-UnifiedEnvironment</c>. Consider using wildcards if needed." Write-PSFMessage -Level Important -Message $messageString Stop-PSFFunction -Message "Stopping because environment was NOT found based on the id." ` -Exception $([System.Exception]::new($($messageString -replace '<[^>]+>', ''))) return } $build = $envObj.PpacProvApp if ($Download) { $downloadDir = "$Path\$($envObj.PpacProvApp)" New-Item -Path $downloadDir ` -ItemType Directory ` -Force ` -WarningAction SilentlyContinue > $null } $baseUri = $envObj.PpacEnvUri + "/" #! Very important to have the trailing slash $secureToken = (Get-AzAccessToken -ResourceUrl $baseUri -AsSecureString).Token $tokenWebApiValue = ConvertFrom-SecureString -AsPlainText -SecureString $secureToken $headers = @{ "Correlationid" = [guid]::NewGuid().ToString() "Dataverseenvironmenturi" = $envObj.PpacEnvUri "Authorization" = "Bearer $($tokenWebApiValue)" "Odata-Maxversion" = "4.0" "Odata-Version" = "4.0" "Accept" = "application/json" } $colFiles = @( foreach ($endpoint in $endpoints) { # Skip if not in requested file types if ($endpoint -notin $colFileTypes) { continue } $localUri = "https://developertools.powerplatform.microsoft.com/api/clientmetadata/$($endpoint.ToLower())?version=$($envObj.PpacProvApp.Replace(".", "-"))" [PsCustomObject][Ordered]@{ "Type" = $endpoint "Build" = $envObj.PpacProvApp "Uri" = $(Invoke-RestMethod -Method Get ` -Uri $localUri ` -Headers $headers) } | Select-PSFObject -TypeName "D365Bap.Tools.UdeDeveloperFile" ` -Property * } ) if (-not $Download) { $colFiles return } Write-PSFMessage -Level Important -Message "Will start the download of the files. It will open a separate PowerShell window for each:" $retryCount = 0 $maxRetries = 5 do { $retryCount++ $processes = @() # Start a new PowerShell window for each download to allow parallel downloads # Each window will remain open after download to allow user to validate the download foreach ($fileObj in $colFiles) { $uriQuery = Split-Path $fileObj.Uri -Leaf $fileName = $uriQuery.Split("?")[0] $outputPath = Join-Path $downloadDir $fileName $fileObj | Add-Member -NotePropertyName "Path" -NotePropertyValue $outputPath -Force if ([System.IO.Path]::Exists($outputPath)) { Write-PSFMessage -Level Important -Message " - Skipping <c='em'>$fileName</c> as it already <c='em'>exists</c>" continue } Write-PSFMessage -Level Important -Message " - <c='em'>$fileName</c>" # Command to run in new window: azcopy copy, then pause for validation $command = @" $executable copy '$($fileObj.Uri)' '$outputPath'; if(-not [System.IO.File]::Exists('$outputPath')){ Write-Host 'Download failed. Review the logs for more information.' -ForegroundColor Red Read-Host -Prompt 'Press Enter to close this window' }; "@ $process = Start-Process -FilePath "powershell.exe" ` -ArgumentList "-Command", $command ` -WindowStyle Normal ` -PassThru $processes += $process } Write-PSFMessage -Level Important -Message "Will await the completion of <c='em'>all</c> file downloads." if ($processes.Count -gt 0 ) { Wait-Process -Id $processes.Id > $null } } while ( ($colFiles | ` Where-Object { -not [System.IO.File]::Exists($_.Path) }) ` -and $retryCount -lt $maxRetries ) foreach ($fileObj in $colFiles) { if (-not [System.IO.File]::Exists($fileObj.Path)) { Write-PSFMessage -Level Important -Message "File <c='em'>$($fileObj.Path)</c> does not exist. It seems the download failed. Please review the logs in the respective PowerShell window for more information." Stop-PSFFunction -Message "Stopping because at least one file download failed." ` -Exception $([System.Exception]::new("File $($fileObj.Path) does not exist. Download failed. Please review the logs in the respective PowerShell window for more information.")) continue } Unblock-File -Path $fileObj.Path } if (Test-PSFFunctionInterrupt) { return } # Output the details to the user $colFiles # If we downloaded the SystemMetadata, we extract it - otherwise it is useless $zipFile = $colFiles | ` Where-Object type -eq 'SystemMetadata' | ` Select-Object -First 1 ` -ExpandProperty Path $pathPackages = "$env:LOCALAPPDATA\Microsoft\Dynamics365\$build" if (-not [System.IO.Path]::Exists("$pathPackages\PackagesLocalDirectory")) { New-Item -Path "$pathPackages" ` -ItemType Directory ` -Force ` -WarningAction SilentlyContinue > $null } elseif (-not $ClearSystemPackages) { $zipObj = [IO.Compression.ZipFile]::OpenRead($zipFile) $zipPackages = ($zipObj.Entries | ` Where-Object Length -eq 0 | ` Where-Object FullName -match '^[^/]+/$' ).Count $localPackages = (Get-ChildItem -Path "$pathPackages\PackagesLocalDirectory" -Directory).Count $zipObj.Dispose() [GC]::Collect() [GC]::WaitForPendingFinalizers() Write-PSFMessage -Level Important -Message "The PackagesLocalDirectory already exists. If you want to re-extract the file, please run this command again with the switch <c='em'>-ClearSystemPackages</c>." Write-PSFMessage -Level Important -Message "Current number of packages in the zip file: <c='em'>$zipPackages</c>. Current number of packages in the local directory: <c='em'>$localPackages</c>." Stop-PSFFunction -Message "Stopping as PackagesLocalDirectory already exists." ` -Exception $([System.Exception]::new("PackagesLocalDirectory already exists. If you want to re-extract the file, please run this command again with the switch -ClearSystemPackages.")) return } Write-PSFMessage -Level Important -Message "Will extract the <c='em'>PackagesLocalDirectory.zip</c> file. It will take some minutes..." [IO.Compression.ZipFile]::ExtractToDirectory($zipFile, "$pathPackages\PackagesLocalDirectory", $true) Write-PSFMessage -Level Important -Message "Extraction completed..." [GC]::Collect() [GC]::WaitForPendingFinalizers() } end { } } |