From b0529f971390806504f98c1a0cd9d399cdbbdc0a Mon Sep 17 00:00:00 2001 From: Robert Onulak Date: Thu, 13 Sep 2018 08:09:19 -0700 Subject: [PATCH 01/11] Work in progress on mac install support. Restructures Install-UnitySetupInstance to handle ordering of installs slightly differently. Breaks out downloading installers into a separate function. --- UnitySetup/UnitySetup.psm1 | 298 ++++++++++++++++++++++++++++++------- 1 file changed, 242 insertions(+), 56 deletions(-) diff --git a/UnitySetup/UnitySetup.psm1 b/UnitySetup/UnitySetup.psm1 index 59f4759..c68fe0d 100644 --- a/UnitySetup/UnitySetup.psm1 +++ b/UnitySetup/UnitySetup.psm1 @@ -427,6 +427,134 @@ function Find-UnitySetupInstaller { } | Sort-Object -Property ComponentType } +function Test-UnitySetupInstance { + [CmdletBinding()] + param( + [parameter(Mandatory = $false)] + [UnityVersion] $Version, + + [parameter(Mandatory = $false)] + [string] $Path + ) + + $instance = Get-UnitySetupInstance | Select-UnitySetupInstance -Version $Version -Path $Path + return $null -ne $instance +} + +<# +.Synopsis + Select installers by a version and/or components. +.DESCRIPTION + Filters a list of `UnitySetupInstaller` down to a specific version and/or specific components. +.PARAMETER Installers + List of installers that needs to be reduced. +.PARAMETER Version + What version of UnitySetupInstaller that you want to keep. +.PARAMETER Components + What components should be maintained. +.EXAMPLE + $installers = Find-UnitySetupInstaller -Version 2017.3.0f3 + $installers += Find-UnitySetupInstaller -Version 2018.2.5f1 + $installers | Select-UnitySetupInstaller -Component Windows,Linux,Mac +#> +function Select-UnitySetupInstaller { + [CmdletBinding()] + param( + [parameter(ValueFromPipeline = $true)] + [UnitySetupInstaller[]] $Installers, + + [parameter(Mandatory = $false)] + [UnityVersion] $Version, + + [parameter(Mandatory = $false)] + [UnitySetupComponent] $Components = [UnitySetupComponent]::All + ) + begin { + $selectedInstallers = @() + } + process { + # Keep only the matching version specified. + if ( $PSBoundParameters.ContainsKey('Version') ) { + $Installers = $Installers | Where-Object { [UnityVersion]::Compare($_.Version, $Version) -eq 0 } + } + + # Keep only the matching component(s). + $Installers = $Installers | Where-Object { $Components -band $_.ComponentType } | ForEach-Object { $_ } + + $selectedInstallers += $Installers + } + end { + return $selectedInstallers + } +} + +function Request-UnitySetupInstaller { + [CmdletBinding()] + param( + [parameter(ValueFromPipeline = $true)] + [UnitySetupInstaller[]] $Installers, + + [parameter(Mandatory = $false)] + [string]$Cache = [io.Path]::Combine("~", ".unitysetup") + ) + begin { + # Note that this has to happen before calculating the full path since + # Resolve-Path throws an exception on missing paths. + if (!(Test-Path $Cache -PathType Container)) { + New-Item $Cache -ItemType Directory -ErrorAction Stop | Out-Null + } + + # Expanding '~' to the absolute path on the system. `WebClient` on macOS asumes + # relative path. macOS also treats alt directory separators as part of the file + # name and this corrects the separators to current environment. + $fullCachePath = (Resolve-Path -Path $Cache).Path + + $downloads = @() + } + process { + $Installers | ForEach-Object { + $installerFileName = [io.Path]::GetFileName($_.DownloadUrl) + $destination = [io.Path]::Combine($fullCachePath, "Installers", "Unity-$($_.Version)", "$installerFileName") + + # Already downloaded? + if ( Test-Path $destination ) { + $destinationItem = Get-Item $destination + if ( ($destinationItem.Length -eq $_.Length ) -and + ($destinationItem.LastWriteTime -eq $_.LastModified) ) { + Write-Verbose "Skipping download because it's already in the cache: $($_.DownloadUrl)" + + $downloads += ,$destination + return + } + } + + $destinationDirectory = [io.path]::GetDirectoryName($destination) + if (!(Test-Path $destinationDirectory -PathType Container)) { + New-Item "$destinationDirectory" -ItemType Directory | Out-Null + } + + try + { + Write-Verbose "Downloading $($_.DownloadUrl) to $destination" + (New-Object System.Net.WebClient).DownloadFile($_.DownloadUrl, $destination) + + # Re-writes the last modified time for ensuring downloads cache. + $downloadedFile = Get-Item $destination + $downloadedFile.LastWriteTime = $_.LastModified + + $downloads += ,$destination + } + catch [System.Net.WebException] + { + Write-Error "Failed downloading $($installerFileName): $($_.Exception.Message)" + } + } + } + end { + return $downloads + } +} + <# .Synopsis Installs a UnitySetup instance. @@ -434,14 +562,20 @@ function Find-UnitySetupInstaller { Downloads and installs UnitySetup installers found via Find-UnitySetupInstaller. .PARAMETER Installers What installers would you like to download and execute? +.PARAMETER BasePath + Under what base patterns is Unity customly installed at. .PARAMETER Destination Where would you like the UnitySetup instance installed? .PARAMETER Cache - Where should the installers be cached. This defaults to $env:USERPROFILE\.unitysetup. + Where should the installers be cached. This defaults to ~\.unitysetup. .EXAMPLE Find-UnitySetupInstaller -Version 2017.3.0f3 | Install-UnitySetupInstance .EXAMPLE Find-UnitySetupInstaller -Version 2017.3.0f3 | Install-UnitySetupInstance -Destination D:\Unity-2017.3.0f3 +.EXAMPLE + Find-UnitySetupInstaller -Version 2017.3.0f3 | Install-UnitySetupInstance -BasePath D:\UnitySetup\ +.EXAMPLE + Find-UnitySetupInstaller -Version 2017.3.0f3 | Install-UnitySetupInstance -BasePath D:\UnitySetup\ -Destination Unity-2017 #> function Install-UnitySetupInstance { [CmdletBinding()] @@ -449,79 +583,119 @@ function Install-UnitySetupInstance { [parameter(ValueFromPipeline = $true)] [UnitySetupInstaller[]] $Installers, + [parameter(Mandatory = $false)] + [string]$BasePath, + [parameter(Mandatory = $false)] [string]$Destination, [parameter(Mandatory = $false)] - [string]$Cache = [io.Path]::Combine($env:USERPROFILE, ".unitysetup") + [string]$Cache = [io.Path]::Combine("~", ".unitysetup") ) + begin { + $currentOS = Get-OperatingSystem + if ($currentOS == [OperatingSystem]::Linux) { + throw "Install-UnitySetupInstance has not been implemented on the Linux platform. Contributions welcomed!"; + } + + if ( -not $PSBoundParameters.ContainsKey('BasePath') ) { + $defaultInstallPath = switch ($currentOS) { + ([OperatingSystem]::Windows) { 'C:\Program Files\Unity' } + ([OperatingSystem]::Linux) { throw "Install-UnitySetupInstance has not been implemented on the Linux platform. Contributions welcomed!"; } + ([OperatingSystem]::Mac) { '/Applications/Unity' } + } + } + else { + $defaultInstallPath = $BasePath + } + + $unitySetupInstances = Get-UnitySetupInstance -BasePath $BasePath + $versionInstallers = @{} + } process { - if (!(Test-Path $Cache -PathType Container)) { - New-Item $Cache -ItemType Directory -ErrorAction Stop | Out-Null + # Sort each installer received from the pipe into versions + $Installers | ForEach-Object { + $versionInstallers[$_.Version] += , $_ } + } + end { + # foreach unity version + # If macOS, move previous install back to default directory + # Install main Unity setup installer first + # Install all components to default Unity version + # If macOS, move install to versioned directory + $versionInstallers.Keys | ForEach-Object { + $installVersion = $_ + $installerInstances = $versionInstallers[$installVersion] + + if ($currentOS == [OperatingSystem]::Mac) { + # On macOS we must notify the user to take an action if the default location + # is currently in use. Either there's a previous version of Unity installed + # manually or another install through UnitySetup possibly failed. + if (Test-UnitySetupInstance -Path /Applications/Unity/) { + # TODO: Work in a `$host.ui.PromptForChoice` / -Force param for resolving this. + throw "Install-UnitySetupInstance has not yet handled working around the base install directory already existing. Please move this manually and try again. Contributions welcomed!"; + } - $localInstallers = @() - $localDestinations = @() - $downloadSource = @() - $downloadDest = @() - foreach ( $i in $Installers) { - $fileName = [io.Path]::GetFileName($i.DownloadUrl) - $destPath = [io.Path]::Combine($Cache, "Installers\Unity-$($i.Version)\$fileName") + } - $localInstallers += , $destPath - if ($Destination) { - $localDestinations += , $Destination + if ( $PSBoundParameters.ContainsKey('Destination') ) { + # Slight API change here. If BasePath is also provided treat Destination as a relative path. + if ( $PSBoundParameters.ContainsKey('BasePath') ) { + $installPath = $Destination + } + else { + $installPath = [io.path]::Combine($BasePath, $Destination) + } } else { - $localDestinations += , "C:\Program Files\Unity-$($i.Version)" + $installPath = "$defaultInstallPath-$installVersion" } - if ( Test-Path $destPath ) { - $destItem = Get-Item $destPath - if ( ($destItem.Length -eq $i.Length ) -and ($destItem.LastWriteTime -eq $i.LastModified) ) { - Write-Verbose "Skipping download because it's already in the cache: $($i.DownloadUrl)" - continue - } - } + $installerPaths = $installerInstances | Request-UnitySetupInstaller -Cache $Cache - $downloadSource += $i.DownloadUrl - $downloadDest += $destPath - } + # TODO: Install Unity component first - if ( $downloadSource.Length -gt 0 ) { - for ($i = 0; $i -lt $downloadSource.Length; $i++) { - Write-Verbose "Downloading $($downloadSource[$i]) to $($downloadDest[$i])" - $destDirectory = [io.path]::GetDirectoryName($downloadDest[$i]) - if (!(Test-Path $destDirectory -PathType Container)) { - New-Item "$destDirectory" -ItemType Directory | Out-Null + foreach ($componentInstallerPath in $installerPaths) { + switch ($currentOS) { + ([OperatingSystem]::Windows) { + $startProcessArgs = @{ + 'FilePath' = $_; + 'ArgumentList' = @("/S", "/D=$installPath"); + 'PassThru' = $true; + 'Wait' = $true; + } + } + ([OperatingSystem]::Linux) { + throw "Install-UnitySetupInstance has not been implemented on the Linux platform. Contributions welcomed!"; + } + ([OperatingSystem]::Mac) { + $startProcessArgs = @{ + 'FilePath' = $installer; + 'ArgumentList' = @("/S", "/D=$destination"); + 'PassThru' = $true; + 'Wait' = $true; + } + } + } + + Write-Verbose "$(Get-Date): Installing $installer to $destination." + $process = Start-Process @startProcessArgs + if ( $process ) { + if ( $process.ExitCode -ne 0) { + Write-Error "$(Get-Date): Failed with exit code: $($process.ExitCode)" + } + else { + Write-Verbose "$(Get-Date): Succeeded." + } } - - (New-Object System.Net.WebClient).DownloadFile($downloadSource[$i], $downloadDest[$i]) } - } - - for ($i = 0; $i -lt $localInstallers.Length; $i++) { - $installer = $localInstallers[$i] - $destination = $localDestinations[$i] - $startProcessArgs = @{ - 'FilePath' = $installer; - 'ArgumentList' = @("/S", "/D=$destination"); - 'PassThru' = $true; - 'Wait' = $true; - } - - Write-Verbose "$(Get-Date): Installing $installer to $destination." - $process = Start-Process @startProcessArgs - if ( $process ) { - if ( $process.ExitCode -ne 0) { - Write-Error "$(Get-Date): Failed with exit code: $($process.ExitCode)" - } - else { - Write-Verbose "$(Get-Date): Succeeded." - } + # Move the install from the staging area to the desired destination + if ($currentOS == [OperatingSystem]::Mac) { + Move-Item -Path /Applications/Unity/ -Destination $installPath } } } @@ -626,14 +800,16 @@ function Get-UnitySetupInstance { Select the latest version available. .PARAMETER Version Select only instances matching Version. -.PARAMETER Project - Select only instances matching the version of the project at Project. +.PARAMETER Path + Select only instances matching the project at the provided path. .PARAMETER instances The list of instances to Select from. .EXAMPLE Get-UnitySetupInstance | Select-UnitySetupInstance -Latest .EXAMPLE Get-UnitySetupInstance | Select-UnitySetupInstance -Version 2017.1.0f3 +.EXAMPLE + Get-UnitySetupInstance | Select-UnitySetupInstance -Path (Get-Item /Applications/Unity*) #> function Select-UnitySetupInstance { [CmdletBinding()] @@ -644,11 +820,21 @@ function Select-UnitySetupInstance { [parameter(Mandatory = $false)] [UnityVersion] $Version, + [parameter(Mandatory = $false)] + [string] $Path, + [parameter(Mandatory = $true, ValueFromPipeline = $true)] [UnitySetupInstance[]] $Instances ) process { + if ( $PSBoundParameters.ContainsKey('Path') ) { + $Path = $Path.TrimEnd([io.path]::DirectorySeparatorChar) + $Instances = $Instances | Where-Object { + $Path -eq (Get-Item $_.Path).FullName.TrimEnd([io.path]::DirectorySeparatorChar) + } + } + if ( $Version ) { $Instances = $Instances | Where-Object { [UnityVersion]::Compare($_.Version, $Version) -eq 0 } } From bc79f16390a6241bf2c66d9807f1f0458756ba7f Mon Sep 17 00:00:00 2001 From: Robert Onulak Date: Fri, 14 Sep 2018 08:42:54 -0700 Subject: [PATCH 02/11] Shows progress of WebClient downloads. Creates separate install method for installing a single package. --- UnitySetup/UnitySetup.psm1 | 156 +++++++++++++++++++++++++++---------- 1 file changed, 114 insertions(+), 42 deletions(-) diff --git a/UnitySetup/UnitySetup.psm1 b/UnitySetup/UnitySetup.psm1 index c68fe0d..2b02d55 100644 --- a/UnitySetup/UnitySetup.psm1 +++ b/UnitySetup/UnitySetup.psm1 @@ -29,6 +29,11 @@ enum OperatingSystem { Mac } +class UnitySetupResource { + [UnitySetupComponent] $ComponentType + [string] $Path +} + class UnitySetupInstaller { [UnitySetupComponent] $ComponentType [UnityVersion] $Version @@ -523,9 +528,25 @@ function Request-UnitySetupInstaller { ($destinationItem.LastWriteTime -eq $_.LastModified) ) { Write-Verbose "Skipping download because it's already in the cache: $($_.DownloadUrl)" - $downloads += ,$destination + $resource = New-Object UnitySetupResource -Property @{ + 'ComponentType' = $_.ComponentType + 'Path' = $destination + } + $downloads += , $resource return } + elseif ($destinationItem.Length -eq $_.Length ) { + Write-Verbose "Skipping download because file size is the same: $($_.DownloadUrl)" + + # Re-writes the last modified time for ensuring downloads cache. + $downloadedFile = Get-Item $destination + $downloadedFile.LastWriteTime = $_.LastModified + + $downloadedFile = Get-Item $destination + if ( ($downloadedFile.LastWriteTime -ne $_.LastModified) ) { + Write-Verbose "Modified time not set for $destination" + } + } } $destinationDirectory = [io.path]::GetDirectoryName($destination) @@ -536,13 +557,37 @@ function Request-UnitySetupInstaller { try { Write-Verbose "Downloading $($_.DownloadUrl) to $destination" - (New-Object System.Net.WebClient).DownloadFile($_.DownloadUrl, $destination) + + $webClient = New-Object System.Net.WebClient + Register-ObjectEvent -InputObject $webClient -EventName DownloadFileCompleted ` + -SourceIdentifier Web.DownloadFileCompleted -Action { + $global:isDownloaded = $true + } + Register-ObjectEvent -InputObject $webClient -EventName DownloadProgressChanged ` + -SourceIdentifier Web.DownloadProgressChanged -Action { + $global:DownloadProgressEvent = $event + } + $webClient.DownloadFileAsync($_.DownloadUrl, $destination) + + $totalBytes = $_.Length + while (-not $isDownloaded) { + $receivedBytes = $global:DownloadProgressEvent.SourceArgs.BytesReceived + $progress = [int](($receivedBytes / [double]$totalBytes) * 100) + + Write-Progress -Activity "Downloading $($_.DownloadUrl)..." -Status "$receivedBytes bytes \ $totalBytes bytes" -PercentComplete $progress + [System.Threading.Thread]::Sleep(500) + } + Write-Progress -Activity "Downloading $($_.DownloadUrl)..." -Status "$receivedBytes bytes \ $totalBytes bytes" -Completed # Re-writes the last modified time for ensuring downloads cache. $downloadedFile = Get-Item $destination $downloadedFile.LastWriteTime = $_.LastModified - $downloads += ,$destination + $resource = New-Object UnitySetupResource -Property @{ + 'ComponentType' = $_.ComponentType + 'Path' = $destination + } + $downloads += , $resource } catch [System.Net.WebException] { @@ -555,6 +600,53 @@ function Request-UnitySetupInstaller { } } +function Install-UnitySetupPackage { + [CmdletBinding()] + param( + [parameter(Mandatory = $true)] + [UnitySetupResource] $Package, + + [parameter(Mandatory = $false)] + [string]$Destination + ) + + $currentOS = Get-OperatingSystem + switch ($currentOS) { + ([OperatingSystem]::Windows) { + $startProcessArgs = @{ + 'FilePath' = $Package.Path; + 'ArgumentList' = @("/S", "/D=$Destination"); + 'PassThru' = $true; + 'Wait' = $true; + } + } + ([OperatingSystem]::Linux) { + throw "Install-UnitySetupPackage has not been implemented on the Linux platform. Contributions welcomed!"; + } + ([OperatingSystem]::Mac) { + # Note, ignores $Destination on Mac. + # sudo installer -package $Package.Path -target / + $startProcessArgs = @{ + 'FilePath' = 'sudo'; + 'ArgumentList' = @("installer", "-package", $Package.Path, "-target", "/"); + 'PassThru' = $true; + 'Wait' = $true; + } + } + } + + Write-Verbose "$(Get-Date): Installing $($Package.ComponentType) to $Destination." + $process = Start-Process @startProcessArgs + if ( $process ) { + if ( $process.ExitCode -ne 0) { + Write-Error "$(Get-Date): Failed with exit code: $($process.ExitCode)" + } + else { + Write-Verbose "$(Get-Date): Succeeded." + } + } +} + <# .Synopsis Installs a UnitySetup instance. @@ -637,8 +729,6 @@ function Install-UnitySetupInstance { # TODO: Work in a `$host.ui.PromptForChoice` / -Force param for resolving this. throw "Install-UnitySetupInstance has not yet handled working around the base install directory already existing. Please move this manually and try again. Contributions welcomed!"; } - - } if ( $PSBoundParameters.ContainsKey('Destination') ) { @@ -654,48 +744,30 @@ function Install-UnitySetupInstance { $installPath = "$defaultInstallPath-$installVersion" } + # TODO: Strip out components already installed in the destination. + $installerPaths = $installerInstances | Request-UnitySetupInstaller -Cache $Cache - # TODO: Install Unity component first - - foreach ($componentInstallerPath in $installerPaths) { - switch ($currentOS) { - ([OperatingSystem]::Windows) { - $startProcessArgs = @{ - 'FilePath' = $_; - 'ArgumentList' = @("/S", "/D=$installPath"); - 'PassThru' = $true; - 'Wait' = $true; - } - } - ([OperatingSystem]::Linux) { - throw "Install-UnitySetupInstance has not been implemented on the Linux platform. Contributions welcomed!"; - } - ([OperatingSystem]::Mac) { - $startProcessArgs = @{ - 'FilePath' = $installer; - 'ArgumentList' = @("/S", "/D=$destination"); - 'PassThru' = $true; - 'Wait' = $true; - } - } - } - - Write-Verbose "$(Get-Date): Installing $installer to $destination." - $process = Start-Process @startProcessArgs - if ( $process ) { - if ( $process.ExitCode -ne 0) { - Write-Error "$(Get-Date): Failed with exit code: $($process.ExitCode)" - } - else { - Write-Verbose "$(Get-Date): Succeeded." - } - } + # First install the Unity editor before other components. + $editorComponent = switch ($currentOS) { + ([OperatingSystem]::Windows) { [UnitySetupComponent]::Windows } + ([OperatingSystem]::Linux) { [UnitySetupComponent]::Linux } + ([OperatingSystem]::Mac) { [UnitySetupComponent]::Mac } + } + $editorInstaller = $installerPaths | Where-Object { $_.ComponentType -band $editorComponent } + if ($null -ne $editorInstaller) { + Write-Verbose "Installing $($editorInstaller.ComponentType)" + # Install-UnitySetupPackage -Package $editorInstaller -Destination $destination + } + + $installerPaths | ForEach-Object { + Write-Verbose "Installing $($editorInstaller.ComponentType)" + # Install-UnitySetupPackage -Package $_ -Destination $destination } # Move the install from the staging area to the desired destination if ($currentOS == [OperatingSystem]::Mac) { - Move-Item -Path /Applications/Unity/ -Destination $installPath + #Move-Item -Path /Applications/Unity/ -Destination $installPath } } } @@ -775,7 +847,7 @@ function Get-UnitySetupInstance { } ([OperatingSystem]::Mac) { if (-not $BasePath) { - $BasePath = @('/Applications/Unity*') + $BasePath = @('/Applications/Unity*', '/Applications/Unity/Hub/Editor/*') } $ivyPath = 'Unity.app/Contents/UnityExtensions/Unity/Networking/ivy.xml' } From 1a4197693746e82d4b610d188a8b41c5c981bd77 Mon Sep 17 00:00:00 2001 From: Robert Onulak Date: Fri, 14 Sep 2018 13:52:39 -0700 Subject: [PATCH 03/11] Corrects issues with displaying a progress bar for WebClient downloads. --- UnitySetup/UnitySetup.psm1 | 110 ++++++++++++++++++++++++------------- 1 file changed, 71 insertions(+), 39 deletions(-) diff --git a/UnitySetup/UnitySetup.psm1 b/UnitySetup/UnitySetup.psm1 index 2b02d55..90c011c 100644 --- a/UnitySetup/UnitySetup.psm1 +++ b/UnitySetup/UnitySetup.psm1 @@ -486,13 +486,26 @@ function Select-UnitySetupInstaller { # Keep only the matching component(s). $Installers = $Installers | Where-Object { $Components -band $_.ComponentType } | ForEach-Object { $_ } - $selectedInstallers += $Installers + if ($Installers.Length -ne 0) { + $selectedInstallers += $Installers + } } end { return $selectedInstallers } } +filter Get-FileSize { + return "{0:N2} {1}" -f $( + if ($_ -lt 1kb) { $_, 'Bytes' } + elseif ($_ -lt 1mb) { ($_/1kb), 'KB' } + elseif ($_ -lt 1gb) { ($_/1mb), 'MB' } + elseif ($_ -lt 1tb) { ($_/1gb), 'GB' } + elseif ($_ -lt 1pb) { ($_/1tb), 'TB' } + else { ($_/1pb), 'PB' } + ) +} + function Request-UnitySetupInstaller { [CmdletBinding()] param( @@ -535,18 +548,6 @@ function Request-UnitySetupInstaller { $downloads += , $resource return } - elseif ($destinationItem.Length -eq $_.Length ) { - Write-Verbose "Skipping download because file size is the same: $($_.DownloadUrl)" - - # Re-writes the last modified time for ensuring downloads cache. - $downloadedFile = Get-Item $destination - $downloadedFile.LastWriteTime = $_.LastModified - - $downloadedFile = Get-Item $destination - if ( ($downloadedFile.LastWriteTime -ne $_.LastModified) ) { - Write-Verbose "Modified time not set for $destination" - } - } } $destinationDirectory = [io.path]::GetDirectoryName($destination) @@ -554,45 +555,76 @@ function Request-UnitySetupInstaller { New-Item "$destinationDirectory" -ItemType Directory | Out-Null } + $webClient = New-Object System.Net.WebClient + + # Register to events for showing progress of file download. + Register-ObjectEvent -InputObject $webClient -EventName DownloadProgressChanged -SourceIdentifier DownloadProgressChanged -Action { + $global:DownloadProgressEvent = $event + } | Out-Null + Register-ObjectEvent -InputObject $webClient -EventName DownloadFileCompleted -SourceIdentifier DownloadFileCompleted -Action { + $global:isDownloaded = $true + } | Out-Null + try { - Write-Verbose "Downloading $($_.DownloadUrl) to $destination" + $startTime = Get-Date - $webClient = New-Object System.Net.WebClient - Register-ObjectEvent -InputObject $webClient -EventName DownloadFileCompleted ` - -SourceIdentifier Web.DownloadFileCompleted -Action { - $global:isDownloaded = $true - } - Register-ObjectEvent -InputObject $webClient -EventName DownloadProgressChanged ` - -SourceIdentifier Web.DownloadProgressChanged -Action { - $global:DownloadProgressEvent = $event - } + $global:DownloadProgressEvent = $null + $global:isDownloaded = $false + + Write-Verbose "Downloading $($_.DownloadUrl) to $destination" $webClient.DownloadFileAsync($_.DownloadUrl, $destination) + # Showing progress of file download $totalBytes = $_.Length - while (-not $isDownloaded) { + while (-not $global:isDownloaded) { + if ($null -eq $global:DownloadProgressEvent) { + continue + } + + $elapsedTime = (Get-Date) - $startTime + $receivedBytes = $global:DownloadProgressEvent.SourceArgs.BytesReceived $progress = [int](($receivedBytes / [double]$totalBytes) * 100) - Write-Progress -Activity "Downloading $($_.DownloadUrl)..." -Status "$receivedBytes bytes \ $totalBytes bytes" -PercentComplete $progress - [System.Threading.Thread]::Sleep(500) - } - Write-Progress -Activity "Downloading $($_.DownloadUrl)..." -Status "$receivedBytes bytes \ $totalBytes bytes" -Completed + # Average speed in Mbps + $averageSpeed = ($receivedBytes * 8 / 1mb) / $elapsedTime.TotalSeconds + $secondsRemaining = ($totalBytes - $receivedBytes) * 8 / 1mb / $averageSpeed - # Re-writes the last modified time for ensuring downloads cache. - $downloadedFile = Get-Item $destination - $downloadedFile.LastWriteTime = $_.LastModified + if ([double]::IsInfinity($secondsRemaining)) { + $averageSpeed = 0 + # -1 for Write-Progress prevents seconds remaining from showing. + $secondsRemaining = -1 + } - $resource = New-Object UnitySetupResource -Property @{ - 'ComponentType' = $_.ComponentType - 'Path' = $destination + # TODO: Display in Kbps on slow networks. + Write-Progress -Activity "$("{0:N2}" -f $averageSpeed) Mbps`nDownloading $($_.DownloadUrl)" ` + -Status "$($receivedBytes | Get-FileSize) of $($totalBytes | Get-FileSize)" ` + -SecondsRemaining $secondsRemaining ` + -PercentComplete $progress } - $downloads += , $resource } - catch [System.Net.WebException] - { - Write-Error "Failed downloading $($installerFileName): $($_.Exception.Message)" + catch [System.Net.WebException] { + Write-Error "Failed downloading $installerFileName - $($_.Exception.Message)" } + finally { + Unregister-Event -SourceIdentifier DownloadFileCompleted -Force + Unregister-Event -SourceIdentifier DownloadProgressChanged -Force + + $webClient.Dispose() + + Write-Progress -Activity "Downloading $($_.DownloadUrl)" -Status "Done" -Completed + } + + # Re-writes the last modified time for ensuring downloads are cached properly. + $downloadedFile = Get-Item $destination + $downloadedFile.LastWriteTime = $_.LastModified + + $resource = New-Object UnitySetupResource -Property @{ + 'ComponentType' = $_.ComponentType + 'Path' = $destination + } + $downloads += , $resource } } end { @@ -606,7 +638,7 @@ function Install-UnitySetupPackage { [parameter(Mandatory = $true)] [UnitySetupResource] $Package, - [parameter(Mandatory = $false)] + [parameter(Mandatory = $true)] [string]$Destination ) From d5f9aed59bc56ae2d0beccc12d02f873122bcc76 Mon Sep 17 00:00:00 2001 From: Robert Onulak Date: Fri, 14 Sep 2018 17:13:59 -0700 Subject: [PATCH 04/11] Corrects visual issues on macOS with the Write-Progress and newlines activity string. --- UnitySetup/UnitySetup.psm1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UnitySetup/UnitySetup.psm1 b/UnitySetup/UnitySetup.psm1 index 90c011c..813e23d 100644 --- a/UnitySetup/UnitySetup.psm1 +++ b/UnitySetup/UnitySetup.psm1 @@ -598,7 +598,7 @@ function Request-UnitySetupInstaller { } # TODO: Display in Kbps on slow networks. - Write-Progress -Activity "$("{0:N2}" -f $averageSpeed) Mbps`nDownloading $($_.DownloadUrl)" ` + Write-Progress -Activity "$("{0:N2}" -f $averageSpeed) Mbps | Downloading $installerFileName" ` -Status "$($receivedBytes | Get-FileSize) of $($totalBytes | Get-FileSize)" ` -SecondsRemaining $secondsRemaining ` -PercentComplete $progress From 8f34065e6d3c17eeb895a1f81ee5dec8e6c119f7 Mon Sep 17 00:00:00 2001 From: Robert Onulak Date: Fri, 14 Sep 2018 23:02:20 -0700 Subject: [PATCH 05/11] Improves download speeds to support more than just mbps. Enables package installs and moves final installs to proper directory on macOS. --- UnitySetup/UnitySetup.psm1 | 84 +++++++++++++++++++++++++------------- 1 file changed, 55 insertions(+), 29 deletions(-) diff --git a/UnitySetup/UnitySetup.psm1 b/UnitySetup/UnitySetup.psm1 index 813e23d..38c43cf 100644 --- a/UnitySetup/UnitySetup.psm1 +++ b/UnitySetup/UnitySetup.psm1 @@ -219,7 +219,7 @@ class UnityVersion : System.IComparable { ([OperatingSystem]::Mac) { echo "On Mac" } } .EXAMPLE - if (Get-OperatingSystem == [OperatingSystem]::Linux) { + if (Get-OperatingSystem -eq [OperatingSystem]::Linux) { echo "On Linux" } #> @@ -495,7 +495,7 @@ function Select-UnitySetupInstaller { } } -filter Get-FileSize { +filter Format-Bytes { return "{0:N2} {1}" -f $( if ($_ -lt 1kb) { $_, 'Bytes' } elseif ($_ -lt 1mb) { ($_/1kb), 'KB' } @@ -506,6 +506,30 @@ filter Get-FileSize { ) } +function Format-BitsPerSecond { + [CmdletBinding()] + param( + [parameter(Mandatory = $true)] + [int] $Bytes, + + [parameter(Mandatory = $true)] + [int] $Seconds + ) + if ($Seconds -le 0.001) { + return "0 Bps" + } + # Convert from bytes to bits + $Bits = ($Bytes * 8) / $Seconds + return "{0:N2} {1}" -f $( + if ($Bits -lt 1kb) { $Bits, 'Bps' } + elseif ($Bits -lt 1mb) { ($Bits/1kb), 'Kbps' } + elseif ($Bits -lt 1gb) { ($Bits/1mb), 'Mbps' } + elseif ($Bits -lt 1tb) { ($Bits/1gb), 'Gbps' } + elseif ($Bits -lt 1pb) { ($Bits/1tb), 'Tbps' } + else { ($Bits/1pb), 'Pbps' } + ) +} + function Request-UnitySetupInstaller { [CmdletBinding()] param( @@ -587,9 +611,8 @@ function Request-UnitySetupInstaller { $receivedBytes = $global:DownloadProgressEvent.SourceArgs.BytesReceived $progress = [int](($receivedBytes / [double]$totalBytes) * 100) - # Average speed in Mbps - $averageSpeed = ($receivedBytes * 8 / 1mb) / $elapsedTime.TotalSeconds - $secondsRemaining = ($totalBytes - $receivedBytes) * 8 / 1mb / $averageSpeed + $averageSpeed = $receivedBytes / $elapsedTime.TotalSeconds + $secondsRemaining = ($totalBytes - $receivedBytes) / $averageSpeed if ([double]::IsInfinity($secondsRemaining)) { $averageSpeed = 0 @@ -597,9 +620,10 @@ function Request-UnitySetupInstaller { $secondsRemaining = -1 } - # TODO: Display in Kbps on slow networks. - Write-Progress -Activity "$("{0:N2}" -f $averageSpeed) Mbps | Downloading $installerFileName" ` - -Status "$($receivedBytes | Get-FileSize) of $($totalBytes | Get-FileSize)" ` + $downloadSpeed = Format-BitsPerSecond -Bytes $receivedBytes -Seconds $elapsedTime.TotalSeconds + + Write-Progress -Activity "Downloading $installerFileName | $downloadSpeed" ` + -Status "$($receivedBytes | Format-Bytes) of $($totalBytes | Format-Bytes)" ` -SecondsRemaining $secondsRemaining ` -PercentComplete $progress } @@ -718,7 +742,7 @@ function Install-UnitySetupInstance { ) begin { $currentOS = Get-OperatingSystem - if ($currentOS == [OperatingSystem]::Linux) { + if ($currentOS -eq [OperatingSystem]::Linux) { throw "Install-UnitySetupInstance has not been implemented on the Linux platform. Contributions welcomed!"; } @@ -744,25 +768,10 @@ function Install-UnitySetupInstance { } } end { - # foreach unity version - # If macOS, move previous install back to default directory - # Install main Unity setup installer first - # Install all components to default Unity version - # If macOS, move install to versioned directory $versionInstallers.Keys | ForEach-Object { $installVersion = $_ $installerInstances = $versionInstallers[$installVersion] - if ($currentOS == [OperatingSystem]::Mac) { - # On macOS we must notify the user to take an action if the default location - # is currently in use. Either there's a previous version of Unity installed - # manually or another install through UnitySetup possibly failed. - if (Test-UnitySetupInstance -Path /Applications/Unity/) { - # TODO: Work in a `$host.ui.PromptForChoice` / -Force param for resolving this. - throw "Install-UnitySetupInstance has not yet handled working around the base install directory already existing. Please move this manually and try again. Contributions welcomed!"; - } - } - if ( $PSBoundParameters.ContainsKey('Destination') ) { # Slight API change here. If BasePath is also provided treat Destination as a relative path. if ( $PSBoundParameters.ContainsKey('BasePath') ) { @@ -776,6 +785,18 @@ function Install-UnitySetupInstance { $installPath = "$defaultInstallPath-$installVersion" } + if ($currentOS -eq [OperatingSystem]::Mac) { + # On macOS we must notify the user to take an action if the default location + # is currently in use. Either there's a previous version of Unity installed + # manually or another install through UnitySetup possibly failed. + if (Test-UnitySetupInstance -Path /Applications/Unity/) { + # TODO: Work in a `$host.ui.PromptForChoice` / -Force param for resolving this. + throw "Install-UnitySetupInstance has not yet handled working around the base install directory already existing. Please move this manually and try again. Contributions welcomed!"; + } + + # TODO: Test if $installPath contains a/this version of Unity to move back to /Applications/Unity/ + } + # TODO: Strip out components already installed in the destination. $installerPaths = $installerInstances | Request-UnitySetupInstaller -Cache $Cache @@ -789,17 +810,22 @@ function Install-UnitySetupInstance { $editorInstaller = $installerPaths | Where-Object { $_.ComponentType -band $editorComponent } if ($null -ne $editorInstaller) { Write-Verbose "Installing $($editorInstaller.ComponentType)" - # Install-UnitySetupPackage -Package $editorInstaller -Destination $destination + Install-UnitySetupPackage -Package $editorInstaller -Destination $installPath } $installerPaths | ForEach-Object { - Write-Verbose "Installing $($editorInstaller.ComponentType)" - # Install-UnitySetupPackage -Package $_ -Destination $destination + # Already installed this earlier. Skipping. + if ($_.ComponentType -band $editorComponent) { + return + } + + Write-Verbose "Installing $($_.ComponentType)" + Install-UnitySetupPackage -Package $_ -Destination $installPath } # Move the install from the staging area to the desired destination - if ($currentOS == [OperatingSystem]::Mac) { - #Move-Item -Path /Applications/Unity/ -Destination $installPath + if ($currentOS -eq [OperatingSystem]::Mac) { + Move-Item -Path /Applications/Unity/ -Destination $installPath } } } From 10fa727f8006bf8d921b6861c47b9426e25a6809 Mon Sep 17 00:00:00 2001 From: Robert Onulak Date: Mon, 17 Sep 2018 17:44:14 -0700 Subject: [PATCH 06/11] Enables support for installing Unity to any disk location by leveraging sparse bundle disks. --- UnitySetup/UnitySetup.psm1 | 62 +++++++++++++++++++++++++++++--------- 1 file changed, 48 insertions(+), 14 deletions(-) diff --git a/UnitySetup/UnitySetup.psm1 b/UnitySetup/UnitySetup.psm1 index 38c43cf..f260a1d 100644 --- a/UnitySetup/UnitySetup.psm1 +++ b/UnitySetup/UnitySetup.psm1 @@ -438,11 +438,14 @@ function Test-UnitySetupInstance { [parameter(Mandatory = $false)] [UnityVersion] $Version, + [parameter(Mandatory = $false)] + [string] $BasePath, + [parameter(Mandatory = $false)] [string] $Path ) - $instance = Get-UnitySetupInstance | Select-UnitySetupInstance -Version $Version -Path $Path + $instance = Get-UnitySetupInstance -BasePath $BasePath | Select-UnitySetupInstance -Version $Version -Path $Path return $null -ne $instance } @@ -680,11 +683,11 @@ function Install-UnitySetupPackage { throw "Install-UnitySetupPackage has not been implemented on the Linux platform. Contributions welcomed!"; } ([OperatingSystem]::Mac) { - # Note, ignores $Destination on Mac. + # Note that $Destination has to be a disk path. # sudo installer -package $Package.Path -target / $startProcessArgs = @{ 'FilePath' = 'sudo'; - 'ArgumentList' = @("installer", "-package", $Package.Path, "-target", "/"); + 'ArgumentList' = @("installer", "-package", $Package.Path, "-target", $Destination); 'PassThru' = $true; 'Wait' = $true; } @@ -786,15 +789,28 @@ function Install-UnitySetupInstance { } if ($currentOS -eq [OperatingSystem]::Mac) { - # On macOS we must notify the user to take an action if the default location - # is currently in use. Either there's a previous version of Unity installed - # manually or another install through UnitySetup possibly failed. - if (Test-UnitySetupInstance -Path /Applications/Unity/) { - # TODO: Work in a `$host.ui.PromptForChoice` / -Force param for resolving this. - throw "Install-UnitySetupInstance has not yet handled working around the base install directory already existing. Please move this manually and try again. Contributions welcomed!"; + # Creating sparse bundle to host installing Unity in other locations + $unitySetupBundlePath = [io.path]::Combine($Cache, "UnitySetup.sparsebundle") + if (-not (Test-Path $unitySetupBundlePath)) { + Write-Verbose "Creating new sparse bundle disk image for installation." + & hdiutil create -size 32g -fs 'HFS+' -type 'SPARSEBUNDLE' -volname 'UnitySetup' $unitySetupBundlePath } + Write-Verbose "Mounting sparse bundle disk." + & hdiutil mount $unitySetupBundlePath - # TODO: Test if $installPath contains a/this version of Unity to move back to /Applications/Unity/ + # Previous version failed to remove. Cleaning up! + if (Test-Path /Volumes/UnitySetup/Applications/) { + Write-Verbose "Previous install did not clean up properly. Doing that now." + & sudo rm -Rf /Volumes/UnitySetup/Applications/ + } + + # Copy installed version back to the sparse bundle disk for Unity component installs. + if (Test-UnitySetupInstance -Path $installPath -BasePath $BasePath) { + Write-Verbose "Copying current installation to sparse bundle disk." + # -a for improved recursion to preserve file attributes and symlinks. + # appended '.' is to allow the copy of all files and folders, even hidden. + & sudo cp -a [io.path]::Combine($installPath, '.') /Volumes/UnitySetup/Applications/Unity/ + } } # TODO: Strip out components already installed in the destination. @@ -807,10 +823,17 @@ function Install-UnitySetupInstance { ([OperatingSystem]::Linux) { [UnitySetupComponent]::Linux } ([OperatingSystem]::Mac) { [UnitySetupComponent]::Mac } } + + $packageDestination = $installPath + # Installers in macOS get installed to the sparse bundle disk first. + if ($currentOS -eq [OperatingSystem]::Mac) { + $packageDestination = "/Volumes/UnitySetup/" + } + $editorInstaller = $installerPaths | Where-Object { $_.ComponentType -band $editorComponent } if ($null -ne $editorInstaller) { Write-Verbose "Installing $($editorInstaller.ComponentType)" - Install-UnitySetupPackage -Package $editorInstaller -Destination $installPath + Install-UnitySetupPackage -Package $editorInstaller -Destination $packageDestination } $installerPaths | ForEach-Object { @@ -820,12 +843,23 @@ function Install-UnitySetupInstance { } Write-Verbose "Installing $($_.ComponentType)" - Install-UnitySetupPackage -Package $_ -Destination $installPath + Install-UnitySetupPackage -Package $_ -Destination $packageDestination } - # Move the install from the staging area to the desired destination + # Move the install from the sparse bundle disk to the install directory. if ($currentOS -eq [OperatingSystem]::Mac) { - Move-Item -Path /Applications/Unity/ -Destination $installPath + Write-Verbose "Copying install to $installPath." + # Copy the files to the install directory. + & sudo cp -af /Volumes/UnitySetup/Applications/Unity/ $installPath + Write-Verbose "Freeing sparse bundle disk space and unmounting." + # Ensure the drive is cleaned up. + & sudo rm -Rf /Volumes/UnitySetup/Applications/ + + & hdiutil eject /Volumes/UnitySetup/ + # Free up disk space since deleting items in the volume send them to the trash + # Also note that -batteryallowed enables compacting while not connected to + # power. The compact is quite quick since the volume is small. + & hdiutil compact $unitySetupBundlePath -batteryallowed } } } From 4195789daa57f7a2fafc71b4c837a175d726b298 Mon Sep 17 00:00:00 2001 From: Robert Onulak Date: Wed, 19 Sep 2018 20:33:52 -0700 Subject: [PATCH 07/11] Resolves issues with installing future components to the same install path. --- UnitySetup/UnitySetup.psm1 | 39 +++++++++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/UnitySetup/UnitySetup.psm1 b/UnitySetup/UnitySetup.psm1 index f260a1d..c09c14f 100644 --- a/UnitySetup/UnitySetup.psm1 +++ b/UnitySetup/UnitySetup.psm1 @@ -789,6 +789,15 @@ function Install-UnitySetupInstance { } if ($currentOS -eq [OperatingSystem]::Mac) { + $volumeRoot = "/Volumes/UnitySetup/" + $volumeInstallPath = [io.path]::Combine($volumeRoot, "Applications/Unity/") + + # Make sure the install path ends with a trailing slash. This + # is required in some commands to treat as directory. + if (-not $installPath.EndsWith([io.path]::DirectorySeparatorChar)) { + $installPath += [io.path]::DirectorySeparatorChar + } + # Creating sparse bundle to host installing Unity in other locations $unitySetupBundlePath = [io.path]::Combine($Cache, "UnitySetup.sparsebundle") if (-not (Test-Path $unitySetupBundlePath)) { @@ -799,17 +808,22 @@ function Install-UnitySetupInstance { & hdiutil mount $unitySetupBundlePath # Previous version failed to remove. Cleaning up! - if (Test-Path /Volumes/UnitySetup/Applications/) { + if (Test-Path $volumeInstallPath) { Write-Verbose "Previous install did not clean up properly. Doing that now." - & sudo rm -Rf /Volumes/UnitySetup/Applications/ + & sudo rm -Rf ([io.path]::Combine($volumeRoot, '*')) } # Copy installed version back to the sparse bundle disk for Unity component installs. if (Test-UnitySetupInstance -Path $installPath -BasePath $BasePath) { - Write-Verbose "Copying current installation to sparse bundle disk." - # -a for improved recursion to preserve file attributes and symlinks. - # appended '.' is to allow the copy of all files and folders, even hidden. - & sudo cp -a [io.path]::Combine($installPath, '.') /Volumes/UnitySetup/Applications/Unity/ + Write-Verbose "Copying $installPath to $volumeInstallPath" + + # Ensure the path exists before copying the previous version to the sparse bundle disk. + & mkdir -p $volumeInstallPath + + # Copy the files (-r) and recreate symlinks (-l) to the install directory. + # Preserve permissions (-p) and owner (-o). + # Need to mark the files with read permissions or installs may fail. + & sudo rsync -rlpo $installPath $volumeInstallPath --chmod=+r } } @@ -827,7 +841,7 @@ function Install-UnitySetupInstance { $packageDestination = $installPath # Installers in macOS get installed to the sparse bundle disk first. if ($currentOS -eq [OperatingSystem]::Mac) { - $packageDestination = "/Volumes/UnitySetup/" + $packageDestination = $volumeRoot } $editorInstaller = $installerPaths | Where-Object { $_.ComponentType -band $editorComponent } @@ -849,13 +863,16 @@ function Install-UnitySetupInstance { # Move the install from the sparse bundle disk to the install directory. if ($currentOS -eq [OperatingSystem]::Mac) { Write-Verbose "Copying install to $installPath." - # Copy the files to the install directory. - & sudo cp -af /Volumes/UnitySetup/Applications/Unity/ $installPath + # Copy the files (-r) and recreate symlinks (-l) to the install directory. + # Preserve permissions (-p) and owner (-o). + # chmod gives files read permissions. + & sudo rsync -rlpo $volumeInstallPath $installPath --chmod=+r --remove-source-files + Write-Verbose "Freeing sparse bundle disk space and unmounting." # Ensure the drive is cleaned up. - & sudo rm -Rf /Volumes/UnitySetup/Applications/ + & sudo rm -Rf ([io.path]::Combine($volumeRoot, '*')) - & hdiutil eject /Volumes/UnitySetup/ + & hdiutil eject $volumeRoot # Free up disk space since deleting items in the volume send them to the trash # Also note that -batteryallowed enables compacting while not connected to # power. The compact is quite quick since the volume is small. From 70edffbccf77a1eaf1b79f369dc3073d8875c3ba Mon Sep 17 00:00:00 2001 From: Robert Onulak Date: Thu, 10 Jan 2019 07:45:49 -0800 Subject: [PATCH 08/11] - Addesses PR feedback and exposes / documents methods that users could leverage in external scripts. --- UnitySetup/UnitySetup.psd1 | 3 +++ UnitySetup/UnitySetup.psm1 | 31 +++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/UnitySetup/UnitySetup.psd1 b/UnitySetup/UnitySetup.psd1 index 2bc02e1..e4b2777 100644 --- a/UnitySetup/UnitySetup.psd1 +++ b/UnitySetup/UnitySetup.psd1 @@ -76,8 +76,11 @@ FunctionsToExport = @( 'Find-UnitySetupInstaller', + 'Select-UnitySetupInstaller', + 'Test-UnitySetupInstance', 'Get-UnityProjectInstance', 'Get-UnitySetupInstance', + 'Request-UnitySetupInstaller', 'Install-UnitySetupInstance', 'Select-UnitySetupInstance', 'Uninstall-UnitySetupInstance', diff --git a/UnitySetup/UnitySetup.psm1 b/UnitySetup/UnitySetup.psm1 index 89edfdf..872ecb7 100644 --- a/UnitySetup/UnitySetup.psm1 +++ b/UnitySetup/UnitySetup.psm1 @@ -432,6 +432,22 @@ function Find-UnitySetupInstaller { } | Sort-Object -Property ComponentType } +<# +.Synopsis + Test if a Unity instance is installed. +.DESCRIPTION + Returns the status of a Unity install by Version and/or Path to install. +.PARAMETER Version + What version of Unity are you looking for? +.PARAMETER BasePath + Under what base patterns is Unity customly installed at. +.PARAMETER Path + Exact path you expect Unity to be installed at. +.EXAMPLE + Test-UnitySetupInstance -Version 2017.3.0f3 +.EXAMPLE + Test-UnitySetupInstance -BasePath D:/UnityInstalls/Unity2018 +#> function Test-UnitySetupInstance { [CmdletBinding()] param( @@ -533,6 +549,21 @@ function Format-BitsPerSecond { ) } +<# +.Synopsis + Download specified Unity installers. +.DESCRIPTION + Filters a list of `UnitySetupInstaller` down to a specific version and/or specific components. +.PARAMETER Installers + List of installers that needs to be downloaded. +.PARAMETER Cache + File path where installers will be downloaded to. +.EXAMPLE + $installers = Find-UnitySetupInstaller -Version 2017.3.0f3 + Request-UnitySetupInstaller -Installers $installers +.EXAMPLE + Find-UnitySetupInstaller -Version 2017.3.0f3 | Request-UnitySetupInstaller +#> function Request-UnitySetupInstaller { [CmdletBinding()] param( From 9cc8b4b19af04fa8ae464f6b0e452c194a201eb9 Mon Sep 17 00:00:00 2001 From: Robert Onulak Date: Thu, 10 Jan 2019 08:14:44 -0800 Subject: [PATCH 09/11] - Resolves issue with `Request-UnitySetupInstaller` not completing after all files finished downloading. --- UnitySetup/UnitySetup.psm1 | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/UnitySetup/UnitySetup.psm1 b/UnitySetup/UnitySetup.psm1 index 872ecb7..679fac8 100644 --- a/UnitySetup/UnitySetup.psm1 +++ b/UnitySetup/UnitySetup.psm1 @@ -665,10 +665,11 @@ function Request-UnitySetupInstaller { } } + # Showing progress of all file downloads $totalDownloads = $global:downloadData.Count + do { + $installersDownloaded = 0 - # Showing progress of all file downloads - while ($global:downloadData.Count -gt 0) { $global:downloadData.Keys | ForEach-Object { $installerFileName = $_ $data = $global:downloadData[$installerFileName] @@ -720,7 +721,7 @@ function Request-UnitySetupInstaller { -PercentComplete $progress ` -Id $data.downloadIndex } - } + } while ($installersDownloaded -lt $totalDownloads) } finally { # If the script is stopped, e.g. Ctrl+C, we want to cancel any remaining downloads From 9fe346df78b46ec65019b2cac174ce6e9848f3fd Mon Sep 17 00:00:00 2001 From: Robert Onulak Date: Thu, 10 Jan 2019 08:57:54 -0800 Subject: [PATCH 10/11] - Resolves issue where the number of installers finished downloaded wasn't being calculated. Thus command was still blocking indefinitely. --- UnitySetup/UnitySetup.psm1 | 1 + 1 file changed, 1 insertion(+) diff --git a/UnitySetup/UnitySetup.psm1 b/UnitySetup/UnitySetup.psm1 index 679fac8..11d11c6 100644 --- a/UnitySetup/UnitySetup.psm1 +++ b/UnitySetup/UnitySetup.psm1 @@ -676,6 +676,7 @@ function Request-UnitySetupInstaller { # Finished downloading if ($null -eq $data.webClient) { + ++$installersDownloaded return } if ($data.isDownloaded) { From 3b6b00274b7d67e5268d0395c46bc7b509d2a1b1 Mon Sep 17 00:00:00 2001 From: Robert Onulak Date: Mon, 28 Jan 2019 07:43:25 -0800 Subject: [PATCH 11/11] - Resolves issue with finding Unity 2018.3 or higher for macOS. - Resolves issues if recursive directories do not exist in install path for macOS. --- UnitySetup/UnitySetup.psm1 | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/UnitySetup/UnitySetup.psm1 b/UnitySetup/UnitySetup.psm1 index 53f7acb..2c595ec 100644 --- a/UnitySetup/UnitySetup.psm1 +++ b/UnitySetup/UnitySetup.psm1 @@ -374,7 +374,24 @@ function Find-UnitySetupInstaller { } if ($null -eq $prototypeLink) { - throw "Could not find archives for Unity version $Version" + # Attempt to find Unity version and setup links based off builtin_shaders download. + Write-Verbose "Attempting version search with builtin_shaders fallback" + foreach ($page in $searchPages) { + $webResult = Invoke-WebRequest $page -UseBasicParsing + $prototypeLink = $webResult.Links | Select-Object -ExpandProperty href -ErrorAction SilentlyContinue | Where-Object { + $_ -match "builtin_shaders-$($Version).zip$" + } + + if ($null -ne $prototypeLink) { break } + } + + if ($null -eq $prototypeLink) { + throw "Could not find archives for Unity version $Version" + } + else { + # Regex needs to be reconfigured to parse builtin_shaders's url link + $unitySetupRegEx = "^(.+)\/([a-z0-9]+)\/builtin_shaders-(\d+)\.(\d+)\.(\d+)([fpb])(\d+).zip$" + } } $linkComponents = $prototypeLink -split $unitySetupRegEx -ne "" @@ -959,11 +976,18 @@ function Install-UnitySetupInstance { # Move the install from the sparse bundle disk to the install directory. if ($currentOS -eq [OperatingSystem]::Mac) { + # rsync does not recursively create the directory path. + if (-not (Test-Path $installPath -PathType Container)) + { + Write-Verbose "Creating directory $installPath." + New-Item $installPath -ItemType Directory -ErrorAction Stop | Out-Null + } + Write-Verbose "Copying install to $installPath." # Copy the files (-r) and recreate symlinks (-l) to the install directory. # Preserve permissions (-p) and owner (-o). # chmod gives files read permissions. - & sudo rsync -rlpo $volumeInstallPath $installPath --chmod=+r --remove-source-files + & sudo rsync -rlpo $volumeInstallPath $installPath --chmod="+wr" --remove-source-files Write-Verbose "Freeing sparse bundle disk space and unmounting." # Ensure the drive is cleaned up.