diff --git a/CHANGELOG.md b/CHANGELOG.md index a07db57c..14a73d81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Changelog * [Changelog](#changelog) + * [2.22.2](#2222) * [2.22.1](#2221) * [2.22.0](#2220) * [2.21.3](#2213) @@ -70,6 +71,13 @@ *** +## 2.22.2 + +* [Issue #144](https://github.com/scrthq/PSGSuite/issues/144) + * Updated: `Start-GSDriveFileUpload` to `Dispose()` open streams once uploads are completed. + * Added: `Stop-GSDriveFileUpload` to enable cleanup of any remaining open streams. + * Updated: `Get-GSDriveFileUpload` to `Dispose()` any completed streams that are still open. + ## 2.22.1 * [PR #141](https://github.com/scrthq/PSGSuite/pull/141) - _Thanks, [@dwrusse](https://github.com/dwrusse)!_ diff --git a/PSGSuite/PSGSuite.psd1 b/PSGSuite/PSGSuite.psd1 index ebd66744..2facf821 100644 --- a/PSGSuite/PSGSuite.psd1 +++ b/PSGSuite/PSGSuite.psd1 @@ -12,7 +12,7 @@ RootModule = 'PSGSuite.psm1' # Version number of this module. - ModuleVersion = '2.22.1' + ModuleVersion = '2.22.2' # ID used to uniquely identify this module GUID = '9d751152-e83e-40bb-a6db-4c329092aaec' diff --git a/PSGSuite/Public/Drive/Get-GSDriveFileUploadStatus.ps1 b/PSGSuite/Public/Drive/Get-GSDriveFileUploadStatus.ps1 index bf02c4f0..b229cb1e 100644 --- a/PSGSuite/Public/Drive/Get-GSDriveFileUploadStatus.ps1 +++ b/PSGSuite/Public/Drive/Get-GSDriveFileUploadStatus.ps1 @@ -2,16 +2,16 @@ function Get-GSDriveFileUploadStatus { <# .SYNOPSIS Gets the current Drive file upload status - + .DESCRIPTION Gets the current Drive file upload status - + .PARAMETER Id The upload Id for the task you'd like to retrieve the status of - + .PARAMETER InProgress If passed, only returns upload statuses that are not 'Failed' or 'Completed'. If nothing is returned when passing this parameter, all tracked uploads have stopped - + .EXAMPLE Get-GSDriveFileUploadStatus -InProgress @@ -35,6 +35,16 @@ function Get-GSDriveFileUploadStatus { foreach ($task in $script:DriveUploadTasks) { $elapsed = ((Get-Date) - $task.StartTime) $progress = {$task.Request.GetProgress()}.InvokeReturnAsIs() + if (-not $task.StreamDisposed -and $progress.Status -eq 'Completed') { + try { + Write-Verbose "[$($progress.Status)] Disposing stream of Task Id [$($task.Id)] | File [$($task.File.FullName)]" + $task.Stream.Dispose() + $task.StreamDisposed = $true + } + catch { + Write-Error $_ + } + } $bytesSent = $progress.BytesSent $remaining = try { New-TimeSpan -Seconds $(($elapsed.TotalSeconds / ($bytesSent / ($task.Length))) - $elapsed.TotalSeconds) -ErrorAction Stop @@ -48,49 +58,45 @@ function Get-GSDriveFileUploadStatus { else { 0 } - if ($Id) { - if ($Id -contains $task.Id) { - $obj = [PSCustomObject]@{ - Id = $task.Id - Status = $progress.Status - PercentComplete = $percentComplete - Remaining = $remaining - StartTime = $task.StartTime - Elapsed = $elapsed - File = $task.File.FullName - Length = $task.Length - Parents = $task.Parents - BytesSent = $bytesSent - FileLocked = $(Test-FileLock -Path $task.File) - User = $task.User - Exception = $progress.Exception - } - if (!$InProgress -or $obj.Status -notin @('Failed','Completed')) { - $obj + if (-not $InProgress -or $progress.Status -notin @('Failed','Completed')) { + if ($Id) { + if ($Id -contains $task.Id) { + [PSCustomObject]@{ + Id = $task.Id + Status = $progress.Status + PercentComplete = $percentComplete + Remaining = $remaining + StartTime = $task.StartTime + Elapsed = $elapsed + File = $task.File.FullName + Length = $task.Length + Parents = $task.Parents + BytesSent = $bytesSent + FileLocked = $(Test-FileLock -Path $task.File) + User = $task.User + Exception = $progress.Exception + } } } - } - else { - $obj = [PSCustomObject]@{ - Id = $task.Id - Status = $progress.Status - PercentComplete = $percentComplete - Remaining = $remaining - StartTime = $task.StartTime - Elapsed = $elapsed - File = $task.File.FullName - Length = $task.Length - Parents = $task.Parents - BytesSent = $bytesSent - FileLocked = $(Test-FileLock -Path $task.File) - User = $task.User - Exception = $progress.Exception - } - if (!$InProgress -or $obj.Status -notin @('Failed','Completed')) { - $obj + else { + [PSCustomObject]@{ + Id = $task.Id + Status = $progress.Status + PercentComplete = $percentComplete + Remaining = $remaining + StartTime = $task.StartTime + Elapsed = $elapsed + File = $task.File.FullName + Length = $task.Length + Parents = $task.Parents + BytesSent = $bytesSent + FileLocked = $(Test-FileLock -Path $task.File) + User = $task.User + Exception = $progress.Exception + } } } } } } -} \ No newline at end of file +} diff --git a/PSGSuite/Public/Drive/Start-GSDriveFileUpload.ps1 b/PSGSuite/Public/Drive/Start-GSDriveFileUpload.ps1 index 116e19b8..c53db53d 100644 --- a/PSGSuite/Public/Drive/Start-GSDriveFileUpload.ps1 +++ b/PSGSuite/Public/Drive/Start-GSDriveFileUpload.ps1 @@ -133,6 +133,7 @@ function Start-GSDriveFileUpload { } $id = New-GSDriveFile @newFolPerms | Select-Object -ExpandProperty Id $folIdHash[$_.FullName] = $id + Write-Verbose " + Created: [ID $id] $($_.FullName -replace "^$([RegEx]::Escape($details.FullName))",$details.Name)" } } $details = $recurseList | Where-Object {!$_.PSIsContainer} | Sort-Object FullName @@ -162,27 +163,31 @@ function Start-GSDriveFileUpload { if ($Description) { $body.Description = $Description } - $stream = New-Object 'System.IO.FileStream' $detPart.FullName,'Open','Read' + $stream = New-Object 'System.IO.FileStream' $detPart.FullName,([System.IO.FileMode]::Open),([System.IO.FileAccess]::Read),([System.IO.FileShare]::Delete + [System.IO.FileShare]::ReadWrite) $request = $service.Files.Create($body,$stream,$contentType) $request.QuotaUser = $User $request.SupportsTeamDrives = $true $request.ChunkSize = 512KB $upload = $request.UploadAsync() - $task = $upload.ContinueWith([System.Action[System.Threading.Tasks.Task]] {$stream.Dispose()}) + $task = $upload.ContinueWith([System.Action[System.Threading.Tasks.Task]] {if ($stream) { + $stream.Dispose() + }}) Write-Verbose "[$($detPart.Name)] Upload Id $($upload.Id) has started" if (!$Script:DriveUploadTasks) { $Script:DriveUploadTasks = [System.Collections.ArrayList]@() } $script:DriveUploadTasks += [PSCustomObject]@{ - Id = $upload.Id - File = $detPart - Length = $detPart.Length - SizeInMB = [Math]::Round(($detPart.Length / 1MB),2,[MidPointRounding]::AwayFromZero) - StartTime = $(Get-Date) - Parents = $body.Parents - User = $User - Upload = $upload - Request = $request + Id = $upload.Id + File = $detPart + Length = $detPart.Length + SizeInMB = [Math]::Round(($detPart.Length / 1MB),2,[MidPointRounding]::AwayFromZero) + StartTime = $(Get-Date) + Parents = $body.Parents + User = $User + Upload = $upload + Request = $request + Stream = $stream + StreamDisposed = $false } $taskList += [PSCustomObject]@{ Id = $upload.Id @@ -215,97 +220,115 @@ function Start-GSDriveFileUpload { Write-Error $_ } } + finally { + [Console]::CursorVisible = $true + } } End { - if (!$Wait) { + if (-not $Wait) { $fullTaskList } else { - Watch-GSDriveUpload -Id $fullTaskList.Id -CountUploaded $totalFiles -TotalUploading $totalFiles - $fullStatusList = Get-GSDriveFileUploadStatus -Id $fullTaskList.Id - $failedFiles = $fullStatusList | Where-Object {$_.Status -eq "Failed"} - if (!$failedFiles) { - Write-Verbose "All files uploaded to Google Drive successfully! Total time: $("{0:c}" -f ((Get-Date) - $start) -replace "\..*")" - } - elseif ($RetryCount) { - $totalRetries = 0 - do { - $throttleCount = 0 - $totalThrottleCount = 0 - $taskList = [System.Collections.ArrayList]@() - $fullTaskList = [System.Collections.ArrayList]@() - $details = Get-Item $failedFiles.File - $totalFiles = [int]$totalFiles + $details.Count - $totalRetries++ - Write-Verbose "~ ~ ~ RETRYING [$totalFiles] FAILED FILES [Retry # $totalRetries / $RetryCount] ~ ~ ~" - $details = Get-Item $failedFiles.File - foreach ($detPart in $details) { - $throttleCount++ - $contentType = Get-MimeType $detPart - $body = New-Object 'Google.Apis.Drive.v3.Data.File' -Property @{ - Name = [String]$detPart.Name - } - $parPath = "$(Split-Path $detPart.FullName -Parent)" - $body.Parents = [String[]]$folIdHash[$parPath] - if ($Description) { - $body.Description = $Description - } - $stream = New-Object 'System.IO.FileStream' $detPart.FullName,'Open','Read' - $request = $service.Files.Create($body,$stream,$contentType) - $request.QuotaUser = $User - $request.SupportsTeamDrives = $true - $request.ChunkSize = 512KB - $upload = $request.UploadAsync() - $task = $upload.ContinueWith([System.Action[System.Threading.Tasks.Task]] {$stream.Dispose()}) - Write-Verbose "[$($detPart.Name)] Upload Id $($upload.Id) has started" - if (!$Script:DriveUploadTasks) { - $Script:DriveUploadTasks = [System.Collections.ArrayList]@() - } - $script:DriveUploadTasks += [PSCustomObject]@{ - Id = $upload.Id - File = $detPart - Length = $detPart.Length - SizeInMB = [Math]::Round(($detPart.Length / 1MB),2,[MidPointRounding]::AwayFromZero) - StartTime = $(Get-Date) - Parents = $body.Parents - User = $User - Upload = $upload - Request = $request - } - $taskList += [PSCustomObject]@{ - Id = $upload.Id - File = $detPart - SizeInMB = [Math]::Round(($detPart.Length / 1MB),2,[MidPointRounding]::AwayFromZero) - User = $User - } - $fullTaskList += [PSCustomObject]@{ - Id = $upload.Id - File = $detPart - SizeInMB = [Math]::Round(($detPart.Length / 1MB),2,[MidPointRounding]::AwayFromZero) - User = $User - } - if ($throttleCount -ge $ThrottleLimit) { - $totalThrottleCount += $throttleCount - if ($Wait) { - Watch-GSDriveUpload -Id $taskList.Id -CountUploaded $totalThrottleCount -TotalUploading $totalFiles -Action Retrying - $throttleCount = 0 - $taskList = [System.Collections.ArrayList]@() + try { + Watch-GSDriveUpload -Id $fullTaskList.Id -CountUploaded $totalFiles -TotalUploading $totalFiles + $fullStatusList = Get-GSDriveFileUploadStatus -Id $fullTaskList.Id + $failedFiles = $fullStatusList | Where-Object {$_.Status -eq "Failed"} + if (!$failedFiles) { + Write-Verbose "All files uploaded to Google Drive successfully! Total time: $("{0:c}" -f ((Get-Date) - $start) -replace "\..*")" + } + elseif ($RetryCount) { + $totalRetries = 0 + do { + $throttleCount = 0 + $totalThrottleCount = 0 + $taskList = [System.Collections.ArrayList]@() + $fullTaskList = [System.Collections.ArrayList]@() + $details = Get-Item $failedFiles.File + $totalFiles = [int]$totalFiles + $details.Count + $totalRetries++ + Write-Verbose "~ ~ ~ RETRYING [$totalFiles] FAILED FILES [Retry # $totalRetries / $RetryCount] ~ ~ ~" + $details = Get-Item $failedFiles.File + foreach ($detPart in $details) { + $throttleCount++ + $contentType = Get-MimeType $detPart + $body = New-Object 'Google.Apis.Drive.v3.Data.File' -Property @{ + Name = [String]$detPart.Name + } + $parPath = "$(Split-Path $detPart.FullName -Parent)" + $body.Parents = [String[]]$folIdHash[$parPath] + if ($Description) { + $body.Description = $Description + } + $stream = New-Object 'System.IO.FileStream' $detPart.FullName,([System.IO.FileMode]::Open),([System.IO.FileAccess]::Read),([System.IO.FileShare]::Delete + [System.IO.FileShare]::ReadWrite) + $request = $service.Files.Create($body,$stream,$contentType) + $request.QuotaUser = $User + $request.SupportsTeamDrives = $true + $request.ChunkSize = 512KB + $upload = $request.UploadAsync() + $task = $upload.ContinueWith([System.Action[System.Threading.Tasks.Task]] {$stream.Dispose()}) + Write-Verbose "[$($detPart.Name)] Upload Id $($upload.Id) has started" + if (!$Script:DriveUploadTasks) { + $Script:DriveUploadTasks = [System.Collections.ArrayList]@() + } + $script:DriveUploadTasks += [PSCustomObject]@{ + Id = $upload.Id + File = $detPart + Length = $detPart.Length + SizeInMB = [Math]::Round(($detPart.Length / 1MB),2,[MidPointRounding]::AwayFromZero) + StartTime = $(Get-Date) + Parents = $body.Parents + User = $User + Upload = $upload + Request = $request + Stream = $stream + StreamDisposed = $false + } + $taskList += [PSCustomObject]@{ + Id = $upload.Id + File = $detPart + SizeInMB = [Math]::Round(($detPart.Length / 1MB),2,[MidPointRounding]::AwayFromZero) + User = $User + } + $fullTaskList += [PSCustomObject]@{ + Id = $upload.Id + File = $detPart + SizeInMB = [Math]::Round(($detPart.Length / 1MB),2,[MidPointRounding]::AwayFromZero) + User = $User + } + if ($throttleCount -ge $ThrottleLimit) { + $totalThrottleCount += $throttleCount + if ($Wait) { + Watch-GSDriveUpload -Id $taskList.Id -CountUploaded $totalThrottleCount -TotalUploading $totalFiles -Action Retrying + $throttleCount = 0 + $taskList = [System.Collections.ArrayList]@() + } } } + Watch-GSDriveUpload -Id $fullTaskList.Id -Action Retrying -CountUploaded $totalFiles -TotalUploading $totalFiles + $fullStatusList = Get-GSDriveFileUploadStatus -Id $fullTaskList.Id + $failedFiles = $fullStatusList | Where-Object {$_.Status -eq "Failed"} + } + until (!$failedFiles -or ($totalRetries -ge $RetryCount)) + if ($failedFiles) { + Write-Warning "The following files failed to upload:`n`n$($failedFiles | Select-Object Id,Status,Exception,File | Format-List | Out-String)" + } + elseif (!$failedFiles) { + Write-Verbose "All files uploaded to Google Drive successfully! Total time: $("{0:c}" -f ((Get-Date) - $start) -replace "\..*")" } - Watch-GSDriveUpload -Id $fullTaskList.Id -Action Retrying -CountUploaded $totalFiles -TotalUploading $totalFiles - $fullStatusList = Get-GSDriveFileUploadStatus -Id $fullTaskList.Id - $failedFiles = $fullStatusList | Where-Object {$_.Status -eq "Failed"} } - until (!$failedFiles -or ($totalRetries -ge $RetryCount)) - if ($failedFiles) { - Write-Warning "The following files failed to upload:`n`n$($failedFiles | Select-Object Id,Status,Exception,File | Format-List | Out-String)" + } + catch { + if ($ErrorActionPreference -eq 'Stop') { + $PSCmdlet.ThrowTerminatingError($_) } - elseif (!$failedFiles) { - Write-Verbose "All files uploaded to Google Drive successfully! Total time: $("{0:c}" -f ((Get-Date) - $start) -replace "\..*")" + else { + Write-Error $_ } } - [Console]::CursorVisible = $true + finally { + [Console]::CursorVisible = $true + Stop-GSDriveFileUpload + } } } } diff --git a/PSGSuite/Public/Drive/Stop-GSDriveFileUpload.ps1 b/PSGSuite/Public/Drive/Stop-GSDriveFileUpload.ps1 new file mode 100644 index 00000000..37ad40b2 --- /dev/null +++ b/PSGSuite/Public/Drive/Stop-GSDriveFileUpload.ps1 @@ -0,0 +1,37 @@ +function Stop-GSDriveFileUpload { + <# + .SYNOPSIS + Stops all Drive file uploads in progress and disposes of all streams. + + .DESCRIPTION + Stops all Drive file uploads in progress and disposes of all streams. + + .EXAMPLE + Stop-GSDriveFileUpload + + Stops all Drive file uploads in progress and disposes of all streams. + #> + [cmdletbinding()] + Param () + Begin { + Write-Verbose "Stopping all remaining Drive file uploads!" + } + Process { + foreach ($task in $script:DriveUploadTasks | Where-Object {$_.Stream -and -not $_.StreamDisposed}) { + try { + $progress = {$task.Request.GetProgress()}.InvokeReturnAsIs() + Write-Verbose "[$($progress.Status)] Stopping stream of Task Id [$($task.Id)] | File [$($task.File.FullName)]" + $task.Stream.Dispose() + $task.StreamDisposed = $true + } + catch { + if ($ErrorActionPreference -eq 'Stop') { + $PSCmdlet.ThrowTerminatingError($_) + } + else { + Write-Error $_ + } + } + } + } +} diff --git a/PSGSuite/Public/Drive/Watch-GSDriveUpload.ps1 b/PSGSuite/Public/Drive/Watch-GSDriveUpload.ps1 index b0f0d7a1..a951ffea 100644 --- a/PSGSuite/Public/Drive/Watch-GSDriveUpload.ps1 +++ b/PSGSuite/Public/Drive/Watch-GSDriveUpload.ps1 @@ -2,22 +2,22 @@ function Watch-GSDriveUpload { <# .SYNOPSIS Shows progress in the console of current Drive file uploads - + .DESCRIPTION Shows progress in the console of current Drive file uploads - + .PARAMETER Id The upload Id(s) that you would like to watch - + .PARAMETER Action Whether the action is uploading or retrying. This is mainly for use in Start-GSDriveFileUpload and defaults to 'Uploading' - + .PARAMETER CountUploaded Current file count being uploaded - + .PARAMETER TotalUploading Total file count being uploaded - + .EXAMPLE Watch-GSDriveUpload @@ -47,10 +47,10 @@ function Watch-GSDriveUpload { do { $i = 1 if ($PSBoundParameters.Keys -contains 'Id') { - $statusList = Get-GSDriveFileUploadStatus -Verbose:$false + $statusList = Get-GSDriveFileUploadStatus -Id @($Id) } else { - $statusList = Get-GSDriveFileUploadStatus -Id @($Id) -Verbose:$false + $statusList = Get-GSDriveFileUploadStatus } if ($statusList) { $totalPercent = 0 @@ -76,7 +76,7 @@ function Watch-GSDriveUpload { $totalPercent = $totalPercent / $totalCount $totalSecondsRemaining = $totalSecondsRemaining / $totalCount $parentParams = @{ - Activity = "[$([Math]::Round($totalPercent,4))%] $Action [$curCount / $totalCount] files to Google Drive" + Activity = "[$([Math]::Round($totalPercent,4))%] $Action [$curCount / $totalCount] files to Google Drive" SecondsRemaining = $($statusList.Remaining.TotalSeconds | Sort-Object | Select-Object -Last 1) } if (!($statusList | Where-Object {$_.Status -ne "Completed"})) { @@ -102,10 +102,10 @@ function Watch-GSDriveUpload { $status.Status } $progParams = @{ - Activity = "[$($status.PercentComplete)%] [ID: $($status.Id)] $($statusFmt) file '$($status.File)' to Google Drive$(if($status.Parents){" (Parents: '$($status.Parents -join "', '")')"})" + Activity = "[$($status.PercentComplete)%] [ID: $($status.Id)] $($statusFmt) file '$($status.File)' to Google Drive$(if($status.Parents){" (Parents: '$($status.Parents -join "', '")')"})" SecondsRemaining = $status.Remaining.TotalSeconds - Id = $i - ParentId = 1 + Id = $i + ParentId = 1 } if ($_.Status -eq "Completed") { $progParams['Completed'] = $true @@ -121,4 +121,4 @@ function Watch-GSDriveUpload { } until (!$statusList -or !($statusList | Where-Object {$_.Status -notin @("Failed","Completed")})) } -} \ No newline at end of file +} diff --git a/README.md b/README.md index db6b7c0c..9953730e 100644 --- a/README.md +++ b/README.md @@ -134,217 +134,13 @@ Update-GSCalendarResource Update-GSResource Update-GSSheetValue Export-GSSheet ``` -### Most recent changes - -#### 2.22.1 - -* [PR #141](https://github.com/scrthq/PSGSuite/pull/141) - _Thanks, [@dwrusse](https://github.com/dwrusse)!_ - * Added: `Remove-GSDriveFile` - * Updated: `Get-GSCalendarSubscription` to add support for `List()` requests and added the `ShowHidden` & `ShowDeleted` parameters. - -#### 2.22.0 - -* Miscellaneous: _Config management and portability updates_ - * Added: `Export-PSGSuiteConfig` function to export key parts of your config in a transportable JSON file. - * Added: `Import-PSGSuiteConfig` function to import a config from a JSON file (i.e. one created with `Export-PSGSuiteConfig`) or from a JSON string (i.e. stored in a secure variable in a CI/CD system.) - * Updated: All config functions now store the P12Key or the ClientSecrets JSON string in the encrypted config directly. This is to allow removal of the secrets files as well as enable PSGSuite to run in a contained environment via importing the config from a secure JSON string. - * Updated: `[Get|Set|Switch]-PSGSuiteConfig` to include the P12Key and ClientSecrets parameters that enable housing of the key/secret directly on the encrypted config. - * Updated: If the global PSGSuite variable `$global:PSGSuite` exists during module import, it will default to using that as it's configuration, otherwise it will import the default config if set. - -#### 2.21.3 +### Most recent change -* [Issue #131](https://github.com/scrthq/PSGSuite/issues/131) - * Fixed: Changed `CodeReceiver` to use `PromptCodeReceiver` when client is PowerShell Core, as `LocalServerCodeReceiver` does not appear to redirect correctly and auth fails. Same behavior in Core regardless of OS. -* Miscellaneous - * Added: `OutputType` to all functions that return standard objects. - -#### 2.21.2 - -* [Issue #136](https://github.com/scrthq/PSGSuite/issues/136) - * Fixed: `Start-GSDriveFileUpload` failing when specifying a user other than the Admin user to do the upload as. - -#### 2.21.1 - -* [Issue #131](https://github.com/scrthq/PSGSuite/issues/131) - _Free/standard Google Account support_ - * Fixed: Handling of scopes in `New-GoogleService` for authentication when a client_secrets.json file is used instead of the typical .p12 key. - * Updated: Documentation to show how to use an account that is not a G Suite admin or G Suite user at all with PSGSuite - * Updated: `*-PSGSuiteConfig` commands now store the client_secrets.json string contents directly on the encrypted config once provided either the path or the string contents directly, allowing users to remove any plain text credentials once loaded into the encrypted config. - * Updated: `Get-GSToken` now uses `New-GoogleService` under the hood, so `client_secrets.json` will work with Contacts API. - -#### 2.21.0 - -* [PR #130](https://github.com/scrthq/PSGSuite/pull/130) / [Issue #129](https://github.com/scrthq/PSGSuite/issues/129) - * Added: Support for UserRelations management in `New-GSUser -Relations $relations` and `Update-GSUser -Relations $relations` via `Add-GSUserRelation` helper function. - _Thanks, [@mattwoolnough](https://github.com/mattwoolnough)!_ - * Added: Logic to `Update-GSUser` to enable clearing of all values for user properties `Phones`, `ExternalIds`, `Organizations`, and `Relations` by REST API call via passing `$null` as the value when calling `Update-GSUser`. - _Thanks, [@mattwoolnough](https://github.com/mattwoolnough)!_ -* [Issue #129](https://github.com/scrthq/PSGSuite/issues/129) - * Fixed: Documentation for `Get-GSSheetInfo` around the `Fields` parameter. - * Added: Additional correction of casing for `Fields` values in `Get-GSSheetInfo` so that it will always submit the values using the correct case, even if providing the incorrect case as the value to the parameter. - -#### 2.20.2 - -* [Issue #128](https://github.com/scrthq/PSGSuite/issues/128) - * Added: `Update-GSMobileDevice` to allow taking action on Mobile Devices - * Fixed: Bug in `Remove-GSMobileDevice` with incorrect variable name - -#### 2.20.1 - -* [Issue #121](https://github.com/scrthq/PSGSuite/issues/121) - * Added: `Update-GSGroupMember` to allow setting a group member's Role and/or DeliverySettings -* Miscellaneous - * Added: GitHub release automation to deploy task - * Added: Twitter update automation on new version release to deploy task - -#### 2.20.0 - -* [Issue #115](https://github.com/scrthq/PSGSuite/issues/115) - * Renamed: `Get-GSCalendarEventList` to `Get-GSCalendarEvent` and set the original name as an exported Alias to the new name for backwards compatibility. - * Added: `EventId` parameter to `Get-GSCalendarEvent` to specify individual event ID's to get instead of a filtered list. - * Added: `PrivateExtendedProperty` parameter to `Get-GSCalendarEvent`. - * Added: `SharedExtendedProperty` parameter to `Get-GSCalendarEvent`. - * Added: `PrivateExtendedProperties` parameter to `New-GSCalendarEvent` and `Update-GSCalendarEvent`. - * Added: `SharedExtendedProperties` parameter to `New-GSCalendarEvent` and `Update-GSCalendarEvent`. - * Added: `ExtendedProperties` parameter to `New-GSCalendarEvent` and `Update-GSCalendarEvent`. - * Added: `Id` parameter to `New-GSCalendarEvent` and `Update-GSCalendarEvent`. -* [Issue #117](https://github.com/scrthq/PSGSuite/issues/117) - * Fixed: Type error on `States` parameter of `Get-GSStudentGuardianInvitation`. -* Miscellaneous - * Updated Contributing doc with new Build script steps - * Removed `DebugMode.ps1` script since it's no longer needed (use `build.ps1` instead) - -#### 2.19.0 - -* [PR #113](https://github.com/scrthq/PSGSuite/pull/113) - * Added: `Add-GSUserEmail` to support the Emails property. - _Thanks, [@sguilbault-sherweb](https://github.com/sguilbault-sherweb)!_ - * Updated: `Add-GSUser` and `Update-GSUser` to implement the newly supported `Emails` property. - _Thanks, [@sguilbault-sherweb](https://github.com/sguilbault-sherweb)!_ - * Fixed: Removed `if ($PSCmdlet.ParameterSetName -eq 'Get')` from `New-GSAdminRoleAssignment` that was making the cmdlet fail. - _Thanks, [@sguilbault-sherweb](https://github.com/sguilbault-sherweb)!_ - * Fixed: `New-GSAdminRoleAssignment` help section rewrite. (The help of this function was a copy of the `Get-GSAdminRoleAssignment` cmdlet) - _Thanks, [@sguilbault-sherweb](https://github.com/sguilbault-sherweb)!_ +[Full CHANGELOG here](https://github.com/scrthq/PSGSuite/blob/master/CHANGELOG.md) -#### 2.18.1 +#### 2.22.2 -* [Issue #87](https://github.com/scrthq/PSGSuite/issues/87) - * Added: Additional scopes during Service creation for `Get-GSCourseParticipant` and `Get-GSClassroomUserProfile` to enable pulling of full user profile information. - _Thanks, [@jdstanberry](https://github.com/jdstanberry)!_ -* [Issue #111](https://github.com/scrthq/PSGSuite/issues/111) - * Added: `DisableReminder` switch parameter to `New-GSCalendarEvent` and `Update-GSCalendarEvent` to remove Reminder inheritance from the calendar the event is on as well as any Reminder overload definitions. -* [Issue #53](https://github.com/scrthq/PSGSuite/issues/53) - * Updated: `Get-GSContactList` and `Remove-GSContact` Token retrieval and overall cleanup -* Various/Other - * Updated: `Get-GSToken` to align parameters more with `New-GoogleService` - -#### 2.18.0 - -* [Issue #87](https://github.com/scrthq/PSGSuite/issues/87) - * Added: `Get-GSCourseParticipant` and `Get-GSClassroomUserProfile` now have the `Fields` parameter - * Added: `Sync-GSUserCache` to create a hashtable of users for quick lookups throughout scripts -* [Issue #53](https://github.com/scrthq/PSGSuite/issues/53) via [PR #108](https://github.com/scrthq/PSGSuite/pull/108) - _Thanks, [@dwrusse](https://github.com/dwrusse)!_ - * Added: `Get-GSContactList` - * Added: `Remove-GSContact` -* Other additions via [PR #108](https://github.com/scrthq/PSGSuite/pull/108) - _Thanks, [@dwrusse](https://github.com/dwrusse)!_ - * Added: `Remove-GSCalendarEvent` - * Added: `New-GSGmailLabel` - * Added: `Remove-GSGmailLabel` - -#### 2.17.2 - -* [Issue #103](https://github.com/scrthq/PSGSuite/issues/103) - * Fixed: `SendNotificationEmail` is now correctly defaulting to `$false`, but attempting to actually send the notification email results in an error. This is now corrected. - -#### 2.17.1 - -* Validated deployment via Azure Pipelines - -#### 2.17.0 - -* [Issue #102](https://github.com/scrthq/PSGSuite/issues/102) - * Fixed: `$EncryptionKey` PSM1 parameter now stores the AES key correctly so SecureStrings are encrypted/decrypted as intended. -* [Issue #103](https://github.com/scrthq/PSGSuite/issues/103) - * Updated: `SendNotificationEmail` parameter on `Add-GSDrivePermission` defaults to false for all User & Group permissions that are not ownership transfers. - * Updated: Documentation for `SendNotificationEmail` parameter on `Add-GSDrivePermission` for clarity towards default Google API parameter values. -* Moved: `Get-GSToken` and `New-GoogleService` to Public functions under the Authentication section -* Added: More unit testing for `Get-GSUser` -* Updated: `psake` build script - -#### 2.16.1 - -* Fixed: Module deployment segment in psake script deploying decompiled/broken module - -#### 2.16.0 - -* Updated: Build script to compile module into a single PSM1 file for cleanliness and loading speed improvements - -#### 2.15.4 - -* [Issue #96](https://github.com/scrthq/PSGSuite/issues/96) - * Updated the following on `Get-GSGroup`: - * Set default scope to `Customer` so that getting the list of groups expectedly gets all of them, not just the ones in your primary domain - * Added `Domain` parameter to specify which domain to list groups from your customer account - * Added `Filter` parameter to only list groups matching the Group query syntax - * Moved the `Get-GSGroupListPrivate` private function into the body of `Get-GSGroup` for error clarity -* Others: - * Moved the `Get-GSUserListPrivate` private function into the body of `Get-GSUser` for error clarity - * Improved error handling for User and Message List functions when there are no results. - -#### 2.15.3 - -* [Issue #87](https://github.com/scrthq/PSGSuite/issues/87) - * Fixed `Add-GSCourseParticipant` error: `"Cannot convert the "student@uni.edu" value of type "System.String" to type "Google.Apis.Classroom.v1.Data.Student"."` - * Set `$request.Fields = "*"` for `Get-GSCourseParticipant` and `Get-GSClassroomUserProfile` to return all available fields for the `Profile`, including `EmailAddress` -* [Issue #93](https://github.com/scrthq/PSGSuite/issues/93) - * Added: `MaxToModify` parameter to `Remove-GSGmailMessage` and `Update-GSGmailMessageLabels` in the `Filter` parameter set to prevent removing/updating more messages than expected when using a filter to gather the list of messages to update. -* Added: `Id` alias for `User` parameter on `Get-GSUser` for better pipeline support - -#### 2.15.2 - -* [Pull Request #94](https://github.com/scrthq/PSGSuite/pull/94) **Thanks, [@dwrusse](https://github.com/dwrusse)!** - * Added `Update-GSGmailLabel` to enable updating of Gmail label properties - * Added `Update-GSGmailMessageLabel` enable updating of labels attached to Gmail messages -* [Issue #93](https://github.com/scrthq/PSGSuite/issues/93) - * Updated `Remove-GSGmailMessage` to include a `-Filter` parameter to allow removal of messages matching a filter in a single command - * Improved pipeline support for `Remove-GSGmailMessage` - -#### 2.15.1 - -* [Issue #87](https://github.com/scrthq/PSGSuite/issues/87) - * Added `User` parameter to all Classroom functions to specify which user to authenticate the request as -* [Issue #90](https://github.com/scrthq/PSGSuite/issues/90) - * Added `Update-GSUserPhoto` - * Added `Remove-GSUserPhoto` - -#### 2.15.0 - -* Updated Gmail Delegation functions to use the .NET SDK after Google announced delegation support for the Gmail API -* Cleaned up `Get-GSGmailDelegates` by removing the trailing `s` (now `Get-GSGmailDelegate`). Added the original function as an alias to the new function for backwards compatibility with scripts. -* Removed the `Raw` parameter from `Get-GSGmailDelegate` since it's no longer applicable. -* Enabled `Get-GSGmailDelegate` to perform both Get and List requests (previously only performed List requests) - -#### 2.14.1 - -* [Issue #87](https://github.com/scrthq/PSGSuite/issues/87) - * Removed `Add-Member` calls from `Get-GSCourseParticipant` to resolve item 3 on issue - * Cleaned up `CourseStates` parameter on `Get-GSCourse` to validate against the Enum directly and removed the default parameter value to resolve item 2 on issue - * Cleaned up `State` parameter on `Get-GSStudentGuardianInvitation` to validate against the Enum directly in an effort to prevent the same issue as item 2 - -#### 2.14.0 - -* [Issue #85](https://github.com/scrthq/PSGSuite/issues/85) - * Added Google Classroom support with the following functions: - * `Add-GSCourseParticipant` - * `Confirm-GSCourseInvitation` - * `Get-GSClassroomUserProfile` - * `Get-GSCourse` - * `Get-GSCourseAlias` - * `Get-GSCourseInvitation` - * `Get-GSCourseParticipant` - * `Get-GSStudentGuardian` - * `Get-GSStudentGuardianInvitation` - * `New-GSCourse` - * `New-GSCourseAlias` - * `New-GSCourseInvitation` - * `New-GSStudentGuardianInvitation` - * `Remove-GSCourse` - * `Remove-GSCourseAlias` - * `Remove-GSCourseInvitation` - * `Remove-GSCourseParticipant` - * `Remove-GSStudentGuardian` - * `Update-GSCourse` -* Fixed: `Get-GSToken` Create/Expiry time split issue on macOS caused by difference in `-UFormat %s` (macOS doesn't have trailing milliseconds) -* Fixed: Logic in confirming if UserID is `[decimal]` to prevent unnecessary errors +* [Issue #144](https://github.com/scrthq/PSGSuite/issues/144) + * Updated: `Start-GSDriveFileUpload` to `Dispose()` open streams once uploads are completed. + * Added: `Stop-GSDriveFileUpload` to enable cleanup of any remaining open streams. + * Updated: `Get-GSDriveFileUpload` to `Dispose()` any completed streams that are still open. diff --git a/build.ps1 b/build.ps1 index 44788b3e..176ccd15 100644 --- a/build.ps1 +++ b/build.ps1 @@ -132,7 +132,13 @@ else { " + NuGet API key is not null : $($null -ne $env:NugetApiKey)`n" + " + Commit message matches '!deploy' : $($env:BUILD_SOURCEVERSIONMESSAGE -match '!deploy') [$env:BUILD_SOURCEVERSIONMESSAGE]"| Write-Host -ForegroundColor Green } - 'BuildHelpers','psake' | Resolve-Module @update -Verbose + if (-not (Get-Module BuildHelpers -ListAvailable | Where-Object {$_.Version -eq '2.0.1'})) { + Write-Verbose "Installing BuildHelpers v2.0.1" -Verbose + Install-Module 'BuildHelpers' -RequiredVersion 2.0.1 -Scope CurrentUser -Repository PSGallery -AllowClobber -SkipPublisherCheck -Force + } + Write-Verbose "Importing BuildHelpers v2.0.1" -Verbose + Import-Module 'BuildHelpers' -RequiredVersion 2.0.1 + 'psake' | Resolve-Module @update -Verbose Set-BuildEnvironment -Force Write-Host -ForegroundColor Green "Modules successfully resolved..." Write-Host -ForegroundColor Green "Invoking psake with task list: [ $($Task -join ', ') ]`n"