From 71fe096fe76535629cc70664340bcbd27f9d84c8 Mon Sep 17 00:00:00 2001 From: Nate Ferrell Date: Wed, 20 Mar 2019 01:14:04 -0500 Subject: [PATCH 1/2] pushing updates for v2.25.0 --- CHANGELOG.md | 17 ++ PSGSuite/PSGSuite.psd1 | 2 +- PSGSuite/Private/Get-PSGSuiteConfigNames.ps1 | 14 ++ .../Get-PSGSuiteServiceCache.ps1 | 31 ++++ .../Authentication/New-GoogleService.ps1 | 139 +++++++++------ PSGSuite/Public/Calendar/Get-GSCalendar.ps1 | 139 +++++++++++++++ .../Public/Calendar/Get-GSCalendarACL.ps1 | 85 ++++----- .../Public/Calendar/New-GSCalendarACL.ps1 | 6 +- .../Public/Calendar/Remove-GSCalendarAcl.ps1 | 74 ++++++++ .../Calendar/Remove-GSCalendarEvent.ps1 | 2 +- .../Configuration/Switch-PSGSuiteConfig.ps1 | 162 ++++++++++-------- PSGSuite/Public/Gmail/Get-GSGmailMessage.ps1 | 4 +- .../Public/Gmail/Restore-GSGmailMessage.ps1 | 14 +- PSGSuite/Public/Sheets/Update-GSSheet.ps1 | 83 +++++++++ 14 files changed, 585 insertions(+), 187 deletions(-) create mode 100644 PSGSuite/Private/Get-PSGSuiteConfigNames.ps1 create mode 100644 PSGSuite/Public/Authentication/Get-PSGSuiteServiceCache.ps1 create mode 100644 PSGSuite/Public/Calendar/Get-GSCalendar.ps1 create mode 100644 PSGSuite/Public/Calendar/Remove-GSCalendarAcl.ps1 create mode 100644 PSGSuite/Public/Sheets/Update-GSSheet.ps1 diff --git a/CHANGELOG.md b/CHANGELOG.md index ffb856bd..d8bdceb3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Changelog * [Changelog](#changelog) + * [2.25.0](#2250) * [2.24.0](#2240) * [2.23.2](#2232) * [2.23.1](#2231) @@ -77,6 +78,22 @@ *** +## 2.25.0 + +* [Issue #162](https://github.com/scrthq/PSGSuite/issues/162) + * Updated: `New-GoogleService` now caches Service objects created during the current session. This means that repeated calls will attempt to use an existing Service object from the cache if present, otherwise it will create the Service as usual. + * Updated: `New-GoogleService` Verbose output. To cut down on verbose noisiness, the following verbose output is set: + * New Service created = `Building ServiceAccountCredential from....` + * First use of existing Service = `Using matching cached service for user....` + * Re-use of existing Service = No verbose output (helps cut down on pipeline verbosity where service re-use is expected) + * Added: `Get-PSGSuiteServiceCache` to get the current Service Cache for inspection. +* [Issue #163](https://github.com/scrthq/PSGSuite/issues/163) + * Added: `Get-GSCalendar` to get the CalendarList of a user. + * Added: `Remove-GSCalendarAcl` to remove Access Control List rules from Google Calendars. +* Miscellaneous + * Improved pipeline support for Gmail `*Message` functions and Calendar functions. + * Added tab completion to `Switch-PSGSuiteConfig` for the ConfigName parameter. + ## 2.24.0 * [Issue #159](https://github.com/scrthq/PSGSuite/issues/159) diff --git a/PSGSuite/PSGSuite.psd1 b/PSGSuite/PSGSuite.psd1 index 5973ef22..fc338bd6 100644 --- a/PSGSuite/PSGSuite.psd1 +++ b/PSGSuite/PSGSuite.psd1 @@ -12,7 +12,7 @@ RootModule = 'PSGSuite.psm1' # Version number of this module. - ModuleVersion = '2.24.0' + ModuleVersion = '2.25.0' # ID used to uniquely identify this module GUID = '9d751152-e83e-40bb-a6db-4c329092aaec' diff --git a/PSGSuite/Private/Get-PSGSuiteConfigNames.ps1 b/PSGSuite/Private/Get-PSGSuiteConfigNames.ps1 new file mode 100644 index 00000000..a0011f7e --- /dev/null +++ b/PSGSuite/Private/Get-PSGSuiteConfigNames.ps1 @@ -0,0 +1,14 @@ +function Get-PSGSuiteConfigNames { + <# + .SYNOPSIS + Gets the current config names for tab completion on Switch-PSGSuiteConfig. + + .DESCRIPTION + Gets the current config names for tab completion on Switch-PSGSuiteConfig. + #> + [cmdletbinding()] + Param () + $script:ConfigScope = $Scope + $fullConf = Import-SpecificConfiguration -CompanyName 'SCRT HQ' -Name 'PSGSuite' -Scope $Script:ConfigScope + $fullConf.Keys | Where-Object {$_ -ne 'DefaultConfig'} +} diff --git a/PSGSuite/Public/Authentication/Get-PSGSuiteServiceCache.ps1 b/PSGSuite/Public/Authentication/Get-PSGSuiteServiceCache.ps1 new file mode 100644 index 00000000..e4230a4f --- /dev/null +++ b/PSGSuite/Public/Authentication/Get-PSGSuiteServiceCache.ps1 @@ -0,0 +1,31 @@ +function Get-PSGSuiteServiceCache { + <# + .SYNOPSIS + Returns the dictionary of cached service objects created with New-GoogleService for inspection. + + .DESCRIPTION + Returns the dictionary of cached service objects created with New-GoogleService for inspection. + + The keys in the session cache dictionary are comprised of the following values which are added to the cache whenever a new session is created: + + $SessionKey = @($User,$ServiceType,$(($Scope | Sort-Object) -join ";")) -join ";" + + .EXAMPLE + Get-PSGSuiteServiceCache + #> + [CmdletBinding()] + Param ( + [parameter(Mandatory = $false,Position = 0)] + [Switch] + $IncludeKeys + ) + Begin{ + if (-not $script:_PSGSuiteSessions) { + $script:_PSGSuiteSessions = @{} + } + } + Process { + Write-Verbose "Getting cached session list" + $script:_PSGSuiteSessions.Values + } +} diff --git a/PSGSuite/Public/Authentication/New-GoogleService.ps1 b/PSGSuite/Public/Authentication/New-GoogleService.ps1 index 9b4b9871..02c11930 100644 --- a/PSGSuite/Public/Authentication/New-GoogleService.ps1 +++ b/PSGSuite/Public/Authentication/New-GoogleService.ps1 @@ -13,69 +13,98 @@ function New-GoogleService { [String] $User = $script:PSGSuite.AdminEmail ) + Begin { + if (-not $script:_PSGSuiteSessions) { + $script:_PSGSuiteSessions = @{} + } + $sessionKey = @($User,$ServiceType,$(($Scope | Sort-Object) -join ";")) -join ";" + } Process { - if ($script:PSGSuite.P12KeyPath -or $script:PSGSuite.P12Key) { - try { - Write-Verbose "Building ServiceAccountCredential from P12Key as user '$User'" - if (-not $script:PSGSuite.P12Key) { - $script:PSGSuite.P12Key = ([System.IO.File]::ReadAllBytes($script:PSGSuite.P12KeyPath)) - Set-PSGSuiteConfig -ConfigName $script:PSGSuite.ConfigName -P12Key $script:PSGSuite.P12Key -Verbose:$false + if ($script:_PSGSuiteSessions.ContainsKey($sessionKey)) { + if (-not $script:_PSGSuiteSessions[$sessionKey].Acknowledged) { + Write-Verbose "Using matching cached service for user '$User'" + $script:_PSGSuiteSessions[$sessionKey].Acknowledged = $true + } + $script:_PSGSuiteSessions[$sessionKey] | Select-Object -ExpandProperty Service + } + else { + if ($script:PSGSuite.P12KeyPath -or $script:PSGSuite.P12Key) { + try { + Write-Verbose "Building ServiceAccountCredential from P12Key as user '$User'" + if (-not $script:PSGSuite.P12Key) { + $script:PSGSuite.P12Key = ([System.IO.File]::ReadAllBytes($script:PSGSuite.P12KeyPath)) + Set-PSGSuiteConfig -ConfigName $script:PSGSuite.ConfigName -P12Key $script:PSGSuite.P12Key -Verbose:$false + } + $certificate = New-Object 'System.Security.Cryptography.X509Certificates.X509Certificate2' -ArgumentList ([System.Byte[]]$script:PSGSuite.P12Key),"notasecret",([System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable) + $credential = New-Object 'Google.Apis.Auth.OAuth2.ServiceAccountCredential' (New-Object 'Google.Apis.Auth.OAuth2.ServiceAccountCredential+Initializer' $script:PSGSuite.AppEmail -Property @{ + User = $User + Scopes = [string[]]$Scope + } + ).FromCertificate($certificate) } - $certificate = New-Object 'System.Security.Cryptography.X509Certificates.X509Certificate2' -ArgumentList ([System.Byte[]]$script:PSGSuite.P12Key),"notasecret",([System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable) - $credential = New-Object 'Google.Apis.Auth.OAuth2.ServiceAccountCredential' (New-Object 'Google.Apis.Auth.OAuth2.ServiceAccountCredential+Initializer' $script:PSGSuite.AppEmail -Property @{ - User = $User - Scopes = [string[]]$Scope + catch { + $PSCmdlet.ThrowTerminatingError($_) } - ).FromCertificate($certificate) - } - catch { - $PSCmdlet.ThrowTerminatingError($_) } - } - elseif ($script:PSGSuite.ClientSecretsPath -or $script:PSGSuite.ClientSecrets) { - try { - $ClientSecretsScopes = @( - 'https://www.google.com/m8/feeds' - 'https://mail.google.com' - 'https://www.googleapis.com/auth/gmail.settings.basic' - 'https://www.googleapis.com/auth/gmail.settings.sharing' - 'https://www.googleapis.com/auth/calendar' - 'https://www.googleapis.com/auth/drive' - 'https://www.googleapis.com/auth/tasks' - 'https://www.googleapis.com/auth/tasks.readonly' - ) - if (-not $script:PSGSuite.ClientSecrets) { - $script:PSGSuite.ClientSecrets = ([System.IO.File]::ReadAllText($script:PSGSuite.ClientSecretsPath)) - Set-PSGSuiteConfig -ConfigName $script:PSGSuite.ConfigName -ClientSecrets $script:PSGSuite.ClientSecrets -Verbose:$false + elseif ($script:PSGSuite.ClientSecretsPath -or $script:PSGSuite.ClientSecrets) { + try { + $ClientSecretsScopes = @( + 'https://www.google.com/m8/feeds' + 'https://mail.google.com' + 'https://www.googleapis.com/auth/gmail.settings.basic' + 'https://www.googleapis.com/auth/gmail.settings.sharing' + 'https://www.googleapis.com/auth/calendar' + 'https://www.googleapis.com/auth/drive' + 'https://www.googleapis.com/auth/tasks' + 'https://www.googleapis.com/auth/tasks.readonly' + ) + if (-not $script:PSGSuite.ClientSecrets) { + $script:PSGSuite.ClientSecrets = ([System.IO.File]::ReadAllText($script:PSGSuite.ClientSecretsPath)) + Set-PSGSuiteConfig -ConfigName $script:PSGSuite.ConfigName -ClientSecrets $script:PSGSuite.ClientSecrets -Verbose:$false + } + $credPath = Join-Path (Resolve-Path (Join-Path "~" ".scrthq")) "PSGSuite" + Write-Verbose "Building UserCredentials from ClientSecrets as user '$User' and prompting for authorization if necessary." + $stream = New-Object System.IO.MemoryStream $([System.Text.Encoding]::ASCII.GetBytes(($script:PSGSuite.ClientSecrets))),$null + $credential = [Google.Apis.Auth.OAuth2.GoogleWebAuthorizationBroker]::AuthorizeAsync( + [Google.Apis.Auth.OAuth2.GoogleClientSecrets]::Load($stream).Secrets, + [string[]]$ClientSecretsScopes, + $User, + [System.Threading.CancellationToken]::None, + [Google.Apis.Util.Store.FileDataStore]::new($credPath,$true), + $(if ($PSVersionTable.PSVersion.Major -gt 5) { + New-Object 'Google.Apis.Auth.OAuth2.PromptCodeReceiver' + } + else { + New-Object 'Google.Apis.Auth.OAuth2.LocalServerCodeReceiver' + }) + ).Result + } + catch { + $PSCmdlet.ThrowTerminatingError($_) + } + finally { + if ($stream) { + $stream.Close() + } } - $credPath = Join-Path (Resolve-Path (Join-Path "~" ".scrthq")) "PSGSuite" - Write-Verbose "Building UserCredentials from ClientSecrets as user '$User' and prompting for authorization if necessary." - $stream = New-Object System.IO.MemoryStream $([System.Text.Encoding]::ASCII.GetBytes(($script:PSGSuite.ClientSecrets))),$null - $credential = [Google.Apis.Auth.OAuth2.GoogleWebAuthorizationBroker]::AuthorizeAsync( - [Google.Apis.Auth.OAuth2.GoogleClientSecrets]::Load($stream).Secrets, - [string[]]$ClientSecretsScopes, - $User, - [System.Threading.CancellationToken]::None, - [Google.Apis.Util.Store.FileDataStore]::new($credPath,$true), - $(if($PSVersionTable.PSVersion.Major -gt 5){New-Object 'Google.Apis.Auth.OAuth2.PromptCodeReceiver'}else{New-Object 'Google.Apis.Auth.OAuth2.LocalServerCodeReceiver'}) - ).Result } - catch { - $PSCmdlet.ThrowTerminatingError($_) + else { + $PSCmdlet.ThrowTerminatingError((ThrowTerm "The current config '$($script:PSGSuite.ConfigName)' does not contain a P12KeyPath or a ClientSecretsPath! PSGSuite is unable to build a credential object for the service without a path to a credential file! Please update the configuration to include a path at least one of the two credential types.")) } - finally { - if ($stream) { - $stream.Close() + $svc = New-Object "$ServiceType" (New-Object 'Google.Apis.Services.BaseClientService+Initializer' -Property @{ + HttpClientInitializer = $credential + ApplicationName = "PSGSuite - $env:USERNAME" } - } + ) + $issued = Get-Date + $script:_PSGSuiteSessions[$sessionKey] = ([PSCustomObject]@{ + User = $User + Scope = $Scope + Service = $svc + Issued = $issued + Acknowledged = $false + }) + return $svc } - else { - $PSCmdlet.ThrowTerminatingError((ThrowTerm "The current config '$($script:PSGSuite.ConfigName)' does not contain a P12KeyPath or a ClientSecretsPath! PSGSuite is unable to build a credential object for the service without a path to a credential file! Please update the configuration to include a path at least one of the two credential types.")) - } - New-Object "$ServiceType" (New-Object 'Google.Apis.Services.BaseClientService+Initializer' -Property @{ - HttpClientInitializer = $credential - ApplicationName = "PSGSuite - $env:USERNAME" - } - ) } } diff --git a/PSGSuite/Public/Calendar/Get-GSCalendar.ps1 b/PSGSuite/Public/Calendar/Get-GSCalendar.ps1 new file mode 100644 index 00000000..ee928b9d --- /dev/null +++ b/PSGSuite/Public/Calendar/Get-GSCalendar.ps1 @@ -0,0 +1,139 @@ +function Get-GSCalendar { + <# + .SYNOPSIS + Gets the calendars for a user + + .DESCRIPTION + Gets the calendars for a user + + .PARAMETER CalendarId + The Id of the calendar you would like to get. + + If excluded, returns the list of calendars for the user. + + .PARAMETER User + The primary email or UserID of the user. You can exclude the '@domain.com' to insert the Domain in the config or use the special 'me' to indicate the AdminEmail in the config. + + Defaults to the AdminEmail in the config + + .PARAMETER MinAccessRole + The minimum access role for the user in the returned entries. Optional. The default is no restriction. + + .PARAMETER PageSize + Maximum number of entries returned on one result page. The page size can never be larger than 250 entries. + + .PARAMETER ShowDeleted + Whether to include deleted calendar list entries in the result. Optional. The default is False. + + .PARAMETER ShowHidden + Whether to show hidden entries. Optional. The default is False. + + .PARAMETER SyncToken + Token obtained from the nextSyncToken field returned on the last page of results from the previous list request. It makes the result of this list request contain only entries that have changed since then. + + If only read-only fields such as calendar properties or ACLs have changed, the entry won't be returned. All entries deleted and hidden since the previous list request will always be in the result set and it is not allowed to set showDeleted neither showHidden to False. + + To ensure client state consistency minAccessRole query parameter cannot be specified together with nextSyncToken. If the syncToken expires, the server will respond with a 410 GONE response code and the client should clear its storage and perform a full synchronization without any syncToken. Learn more about incremental synchronization. + + Optional. The default is to return all entries. + #> + [OutputType('Google.Apis.Calendar.v3.Data.CalendarListEntry')] + [cmdletbinding(DefaultParameterSetName = "List")] + Param + ( + [parameter(Mandatory = $true,Position = 0,ParameterSetName = "Get")] + [String[]] + $CalendarId, + [parameter(Mandatory = $false,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)] + [Alias("PrimaryEmail","UserKey","Mail")] + [ValidateNotNullOrEmpty()] + [String[]] + $User = $Script:PSGSuite.AdminEmail, + [parameter(Mandatory = $false,ParameterSetName = "List")] + [Google.Apis.Calendar.v3.CalendarListResource+ListRequest+MinAccessRoleEnum] + $MinAccessRole, + [parameter(Mandatory = $false,ParameterSetName = "List")] + [Alias('MaxResults')] + [ValidateRange(1,250)] + [Int] + $PageSize = 250, + [parameter(Mandatory = $false,ParameterSetName = "List")] + [switch] + $ShowDeleted, + [parameter(Mandatory = $false,ParameterSetName = "List")] + [switch] + $ShowHidden, + [parameter(Mandatory = $false,ParameterSetName = "List")] + [String] + $SyncToken + ) + Process { + foreach ($U in $User) { + if ($U -ceq 'me') { + $U = $Script:PSGSuite.AdminEmail + } + elseif ($U -notlike "*@*.*") { + $U = "$($U)@$($Script:PSGSuite.Domain)" + } + $serviceParams = @{ + Scope = 'https://www.googleapis.com/auth/calendar' + ServiceType = 'Google.Apis.Calendar.v3.CalendarService' + User = $U + } + $service = New-GoogleService @serviceParams + switch ($PSCmdlet.ParameterSetName) { + Get { + foreach ($calId in $CalendarId) { + try { + $request = $service.CalendarList.Get($calId) + Write-Verbose "Getting Calendar Id '$calId' for User '$U'" + $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $U -PassThru + } + catch { + if ($ErrorActionPreference -eq 'Stop') { + $PSCmdlet.ThrowTerminatingError($_) + } + else { + Write-Error $_ + } + } + } + } + List { + try { + $request = $service.CalendarList.List() + foreach ($key in $PSBoundParameters.Keys | Where-Object {$_ -ne 'CalendarId'}) { + if ($request.PSObject.Properties.Name -contains $key) { + $request.$key = $PSBoundParameters[$key] + } + } + if ($PageSize) { + $request.MaxResults = $PageSize + } + Write-Verbose "Getting Calendar List for user '$U'" + [int]$i = 1 + do { + $result = $request.Execute() + $result.Items | Add-Member -MemberType NoteProperty -Name 'User' -Value $U -PassThru + if ($result.NextPageToken) { + $request.PageToken = $result.NextPageToken + } + [int]$retrieved = ($i + $result.Items.Count) - 1 + Write-Verbose "Retrieved $retrieved Calendars..." + [int]$i = $i + $result.Items.Count + } + until (!$result.NextPageToken) + } + catch { + if ($ErrorActionPreference -eq 'Stop') { + $PSCmdlet.ThrowTerminatingError($_) + } + else { + Write-Error $_ + } + } + } + } + } + } +} diff --git a/PSGSuite/Public/Calendar/Get-GSCalendarACL.ps1 b/PSGSuite/Public/Calendar/Get-GSCalendarACL.ps1 index 8d6a85fa..e888e8e4 100644 --- a/PSGSuite/Public/Calendar/Get-GSCalendarACL.ps1 +++ b/PSGSuite/Public/Calendar/Get-GSCalendarACL.ps1 @@ -1,10 +1,10 @@ -function Get-GSCalendarACL { +function Get-GSCalendarAcl { <# .SYNOPSIS - Gets the ACL calendar for a calendar + Gets the Access Control List for a calendar .DESCRIPTION - Gets the ACL for a calendar + Gets the Access Control List for a calendar .PARAMETER User The primary email or UserID of the user. You can exclude the '@domain.com' to insert the Domain in the config or use the special 'me' to indicate the AdminEmail in the config. @@ -36,8 +36,9 @@ function Get-GSCalendarACL { [ValidateNotNullOrEmpty()] [String[]] $User = $Script:PSGSuite.AdminEmail, - [parameter(Mandatory = $false)] - [String] + [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] + [Alias('Id')] + [String[]] $CalendarId = "primary", [parameter(Mandatory = $false, ParameterSetName = 'Get')] [String] @@ -61,49 +62,51 @@ function Get-GSCalendarACL { User = $U } $service = New-GoogleService @serviceParams - try { - switch ($PSCmdlet.ParameterSetName) { - Get { - Write-Verbose "Getting ACL Id '$RuleId' of calendar '$CalendarId' for user '$U'" - $request = $service.Acl.Get($CalendarId,$RuleId) - $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $U -PassThru | Add-Member -MemberType NoteProperty -Name 'CalendarId' -Value $CalendarId -PassThru - } - List { - $request = $service.Acl.List($CalendarId) - foreach ($key in $PSBoundParameters.Keys | Where-Object {$_ -ne 'CalendarId'}) { - switch ($key) { - Default { - if ($request.PSObject.Properties.Name -contains $key) { - $request.$key = $PSBoundParameters[$key] + foreach ($calId in $CalendarId) { + try { + switch ($PSCmdlet.ParameterSetName) { + Get { + Write-Verbose "Getting ACL Id '$RuleId' of calendar '$calId' for user '$U'" + $request = $service.Acl.Get($calId,$RuleId) + $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $U -PassThru | Add-Member -MemberType NoteProperty -Name 'CalendarId' -Value $calId -PassThru + } + List { + $request = $service.Acl.List($calId) + foreach ($key in $PSBoundParameters.Keys | Where-Object {$_ -ne 'CalendarId'}) { + switch ($key) { + Default { + if ($request.PSObject.Properties.Name -contains $key) { + $request.$key = $PSBoundParameters[$key] + } } } } - } - if ($PageSize) { - $request.MaxResults = $PageSize - } - Write-Verbose "Getting ACL List of calendar '$CalendarId' for user '$U'" - [int]$i = 1 - do { - $result = $request.Execute() - $result.Items | Add-Member -MemberType NoteProperty -Name 'User' -Value $U -PassThru | Add-Member -MemberType NoteProperty -Name 'CalendarId' -Value $CalendarId -PassThru - if ($result.NextPageToken) { - $request.PageToken = $result.NextPageToken + if ($PageSize) { + $request.MaxResults = $PageSize } - [int]$retrieved = ($i + $result.Items.Count) - 1 - Write-Verbose "Retrieved $retrieved Calendar Events..." - [int]$i = $i + $result.Items.Count + Write-Verbose "Getting ACL List of calendar '$calId' for user '$U'" + [int]$i = 1 + do { + $result = $request.Execute() + $result.Items | Add-Member -MemberType NoteProperty -Name 'User' -Value $U -PassThru | Add-Member -MemberType NoteProperty -Name 'CalendarId' -Value $calId -PassThru + if ($result.NextPageToken) { + $request.PageToken = $result.NextPageToken + } + [int]$retrieved = ($i + $result.Items.Count) - 1 + Write-Verbose "Retrieved $retrieved Calendar ACLs..." + [int]$i = $i + $result.Items.Count + } + until (!$result.NextPageToken) } - until (!$result.NextPageToken) } } - } - catch { - if ($ErrorActionPreference -eq 'Stop') { - $PSCmdlet.ThrowTerminatingError($_) - } - else { - Write-Error $_ + catch { + if ($ErrorActionPreference -eq 'Stop') { + $PSCmdlet.ThrowTerminatingError($_) + } + else { + Write-Error $_ + } } } } diff --git a/PSGSuite/Public/Calendar/New-GSCalendarACL.ps1 b/PSGSuite/Public/Calendar/New-GSCalendarACL.ps1 index 754ce33c..f4bfc9ef 100644 --- a/PSGSuite/Public/Calendar/New-GSCalendarACL.ps1 +++ b/PSGSuite/Public/Calendar/New-GSCalendarACL.ps1 @@ -1,10 +1,10 @@ -function New-GSCalendarACL { +function New-GSCalendarAcl { <# .SYNOPSIS - Adds Google User to Calendar + Adds a new Access Control Rule to a calendar. .DESCRIPTION - Adds Google User to Calendar + Adds a new Access Control Rule to a calendar. .PARAMETER User The primary email or UserID of the user. You can exclude the '@domain.com' to insert the Domain in the config or use the special 'me' to indicate the AdminEmail in the config. diff --git a/PSGSuite/Public/Calendar/Remove-GSCalendarAcl.ps1 b/PSGSuite/Public/Calendar/Remove-GSCalendarAcl.ps1 new file mode 100644 index 00000000..07aed25a --- /dev/null +++ b/PSGSuite/Public/Calendar/Remove-GSCalendarAcl.ps1 @@ -0,0 +1,74 @@ +function Remove-GSCalendarAcl { + <# + .SYNOPSIS + Removes an Access Control List rule from a calendar. + + .DESCRIPTION + Removes an Access Control List rule from a calendar. + + .PARAMETER User + The primary email or UserID of the user who owns the calendar. You can exclude the '@domain.com' to insert the Domain in the config or use the special 'me' to indicate the AdminEmail in the config. + + .PARAMETER CalendarID + The calendar ID of the calendar you would like to remove the ACL from. + + .PARAMETER RuleId + The ACL rule Id to remove. + + .EXAMPLE + Get-GSCalendar -User joe@domain.com | + Get-GSCalendarACL | + Where-Object {$_.Role -eq 'Owner'} | + Remove-GSCalendarACL + + Gets all the calendars for Joe and finds all ACL rules where + #> + [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = "High")] + Param + ( + [parameter(Mandatory = $true,ValueFromPipelineByPropertyName = $true)] + [Alias("PrimaryEmail","UserKey","Mail")] + [ValidateNotNullOrEmpty()] + [String] + $User, + [parameter(Mandatory = $true,ValueFromPipelineByPropertyName = $true)] + [String] + $CalendarID, + [parameter(Mandatory = $true,ValueFromPipelineByPropertyName = $true)] + [Alias('Id')] + [String[]] + $RuleId + ) + Process { + if ($User -ceq 'me') { + $User = $Script:PSGSuite.AdminEmail + } + elseif ($User -notlike "*@*.*") { + $User = "$($User)@$($Script:PSGSuite.Domain)" + } + $serviceParams = @{ + Scope = 'https://www.googleapis.com/auth/calendar' + ServiceType = 'Google.Apis.Calendar.v3.CalendarService' + User = $User + } + $service = New-GoogleService @serviceParams + foreach ($rule in $RuleId) { + try { + if ($PSCmdlet.ShouldProcess("Deleting ACL Rule Id '$rule' from Calendar '$CalendarID' for user '$User'")) { + Write-Verbose "Deleting ACL Rule Id '$rule' from Calendar '$CalendarID' for user '$User'" + $request = $service.Acl.Delete($CalendarID, $rule) + $request.Execute() + Write-Verbose "ACL Rule Id '$rule' deleted successfully from Calendar '$CalendarID' for user '$User'" + } + } + catch { + if ($ErrorActionPreference -eq 'Stop') { + $PSCmdlet.ThrowTerminatingError($_) + } + else { + Write-Error $_ + } + } + } + } +} diff --git a/PSGSuite/Public/Calendar/Remove-GSCalendarEvent.ps1 b/PSGSuite/Public/Calendar/Remove-GSCalendarEvent.ps1 index a01d6885..bb89069c 100644 --- a/PSGSuite/Public/Calendar/Remove-GSCalendarEvent.ps1 +++ b/PSGSuite/Public/Calendar/Remove-GSCalendarEvent.ps1 @@ -12,7 +12,7 @@ function Remove-GSCalendarEvent { Defaults to the AdminEmail in the config. .PARAMETER CalendarID - The calendar ID of the calendar you would like to list events from. + The calendar ID of the calendar you would like to remove events from. Defaults to the user's primary calendar. diff --git a/PSGSuite/Public/Configuration/Switch-PSGSuiteConfig.ps1 b/PSGSuite/Public/Configuration/Switch-PSGSuiteConfig.ps1 index 793b33d0..7b410261 100644 --- a/PSGSuite/Public/Configuration/Switch-PSGSuiteConfig.ps1 +++ b/PSGSuite/Public/Configuration/Switch-PSGSuiteConfig.ps1 @@ -20,14 +20,9 @@ Switches the config to the "newCustomer" config #> - [CmdletBinding(DefaultParameterSetName = "ConfigName")] - Param - ( - [parameter(Mandatory = $true,Position = 0,ParameterSetName = "ConfigName")] - [ValidateNotNullOrEmpty()] - [String] - $ConfigName, - [parameter(Mandatory = $true,Position = 0,ParameterSetName = "Domain")] + [CmdletBinding(DefaultParameterSetName = "ConfigName",PositionalBinding = $false)] + Param ( + [parameter(Mandatory = $true,ParameterSetName = "Domain")] [ValidateNotNullOrEmpty()] [String] $Domain, @@ -35,84 +30,101 @@ [switch] $SetToDefault ) - if ($script:PSGSuite.Domain -eq $Domain) { - Write-Verbose "Current config is already set to domain '$Domain' --- retaining current config. If you would like to import a different config for the same domain, please use the -ConfigName parameter instead" - if ($SetToDefault) { - Write-Verbose "Setting config name '$($script:PSGSuite.ConfigName)' for domain '$($script:PSGSuite.Domain)' as default" - Set-PSGSuiteConfig -ConfigName $($script:PSGSuite.ConfigName) -SetAsDefaultConfig -Verbose:$false - } - } - elseif ($script:PSGSuite.ConfigName -eq $ConfigName) { - Write-Verbose "Current config is already set to '$ConfigName' --- retaining current config" - if ($SetToDefault) { - Write-Verbose "Setting config name '$($script:PSGSuite.ConfigName)' for domain '$($script:PSGSuite.Domain)' as default" - Set-PSGSuiteConfig -ConfigName $($script:PSGSuite.ConfigName) -SetAsDefaultConfig -Verbose:$false - } + DynamicParam { + $attribute = New-Object System.Management.Automation.ParameterAttribute + $attribute.Position = 0 + $attribute.Mandatory = $true + $attribute.ParameterSetName = "ConfigName" + $attributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute] + $attributeCollection.Add($attribute) + $names = Get-PSGSuiteConfigNames -Verbose:$false + $attributeCollection.Add((New-Object System.Management.Automation.ValidateSetAttribute($names))) + $parameter = New-Object System.Management.Automation.RuntimeDefinedParameter('ConfigName', [String], $attributeCollection) + $paramDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary + $paramDictionary.Add('ConfigName', $parameter) + return $paramDictionary } - else { - function Decrypt { - param($String) - if ($String -is [System.Security.SecureString]) { - [System.Runtime.InteropServices.Marshal]::PtrToStringAuto( - [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR( - $string)) - } - elseif ($String -is [System.String]) { - $String + Process { + $ConfigName = $PSBoundParameters['ConfigName'] + if ($script:PSGSuite.Domain -eq $Domain) { + Write-Verbose "Current config is already set to domain '$Domain' --- retaining current config. If you would like to import a different config for the same domain, please use the -ConfigName parameter instead" + if ($SetToDefault) { + Write-Verbose "Setting config name '$($script:PSGSuite.ConfigName)' for domain '$($script:PSGSuite.Domain)' as default" + Set-PSGSuiteConfig -ConfigName $($script:PSGSuite.ConfigName) -SetAsDefaultConfig -Verbose:$false } } - $fullConf = Import-SpecificConfiguration -CompanyName 'SCRT HQ' -Name 'PSGSuite' -Scope $Script:ConfigScope -Verbose:$false - $defaultConfigName = $fullConf['DefaultConfig'] - $choice = switch ($PSCmdlet.ParameterSetName) { - Domain { - Write-Verbose "Switching active domain to '$Domain'" - $fullConf.Keys | Where-Object {(Decrypt $fullConf[$_]['Domain']) -eq $Domain} - } - ConfigName { - Write-Verbose "Switching active config to '$ConfigName'" - $fullConf.Keys | Where-Object {$_ -eq $ConfigName} + elseif ($script:PSGSuite.ConfigName -eq $ConfigName) { + Write-Verbose "Current config is already set to '$ConfigName' --- retaining current config" + if ($SetToDefault) { + Write-Verbose "Setting config name '$($script:PSGSuite.ConfigName)' for domain '$($script:PSGSuite.Domain)' as default" + Set-PSGSuiteConfig -ConfigName $($script:PSGSuite.ConfigName) -SetAsDefaultConfig -Verbose:$false } } - if ($choice) { - $script:PSGSuite = [PSCustomObject]($fullConf[$choice]) | - Select-Object -Property @{l = 'ConfigName';e = {$choice}}, - @{l = 'P12KeyPath';e = {Decrypt $_.P12KeyPath}}, - P12Key, - @{l = 'ClientSecretsPath';e = {Decrypt $_.ClientSecretsPath}}, - @{l = 'ClientSecrets';e = {Decrypt $_.ClientSecrets}}, - @{l = 'AppEmail';e = {Decrypt $_.AppEmail}}, - @{l = 'AdminEmail';e = {Decrypt $_.AdminEmail}}, - @{l = 'CustomerID';e = {Decrypt $_.CustomerID}}, - @{l = 'Domain';e = {Decrypt $_.Domain}}, - @{l = 'Preference';e = {Decrypt $_.Preference}}, - @{l = 'ServiceAccountClientID';e = {Decrypt $_.ServiceAccountClientID}}, - @{l = 'Webhook';e = { - $dict = @{} - foreach ($key in $_.Webhook.Keys) { - $dict[$key] = (Decrypt $_.Webhook[$key]) - } - $dict - }}, - ConfigPath - if ($SetToDefault) { - if ($defaultConfigName -ne $choice) { - Write-Verbose "Setting config name '$choice' for domain '$($script:PSGSuite.Domain)' as default" - Set-PSGSuiteConfig -ConfigName $choice -SetAsDefaultConfig -Verbose:$false - $env:PSGSuiteDefaultDomain = $script:PSGSuite.Domain - [Environment]::SetEnvironmentVariable("PSGSuiteDefaultDomain", $script:PSGSuite.Domain, "User") + else { + function Decrypt { + param($String) + if ($String -is [System.Security.SecureString]) { + [System.Runtime.InteropServices.Marshal]::PtrToStringAuto( + [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR( + $string)) } - else { - Write-Warning "Config name '$choice' for domain '$($script:PSGSuite.Domain)' is already set to default --- no action taken" + elseif ($String -is [System.String]) { + $String } } - } - else { - switch ($PSCmdlet.ParameterSetName) { + $fullConf = Import-SpecificConfiguration -CompanyName 'SCRT HQ' -Name 'PSGSuite' -Scope $Script:ConfigScope -Verbose:$false + $defaultConfigName = $fullConf['DefaultConfig'] + $choice = switch ($PSCmdlet.ParameterSetName) { Domain { - Write-Warning "No config found for domain '$Domain'! Retaining existing config for domain '$($script:PSGSuite.Domain)'" + Write-Verbose "Switching active domain to '$Domain'" + $fullConf.Keys | Where-Object {(Decrypt $fullConf[$_]['Domain']) -eq $Domain} } ConfigName { - Write-Warning "No config named '$ConfigName' found! Retaining existing config '$($script:PSGSuite.ConfigName)'" + Write-Verbose "Switching active config to '$ConfigName'" + $fullConf.Keys | Where-Object {$_ -eq $ConfigName} + } + } + if ($choice) { + $script:PSGSuite = [PSCustomObject]($fullConf[$choice]) | + Select-Object -Property @{l = 'ConfigName';e = {$choice}}, + @{l = 'P12KeyPath';e = {Decrypt $_.P12KeyPath}}, + P12Key, + @{l = 'ClientSecretsPath';e = {Decrypt $_.ClientSecretsPath}}, + @{l = 'ClientSecrets';e = {Decrypt $_.ClientSecrets}}, + @{l = 'AppEmail';e = {Decrypt $_.AppEmail}}, + @{l = 'AdminEmail';e = {Decrypt $_.AdminEmail}}, + @{l = 'CustomerID';e = {Decrypt $_.CustomerID}}, + @{l = 'Domain';e = {Decrypt $_.Domain}}, + @{l = 'Preference';e = {Decrypt $_.Preference}}, + @{l = 'ServiceAccountClientID';e = {Decrypt $_.ServiceAccountClientID}}, + @{l = 'Webhook';e = { + $dict = @{} + foreach ($key in $_.Webhook.Keys) { + $dict[$key] = (Decrypt $_.Webhook[$key]) + } + $dict + }}, + ConfigPath + if ($SetToDefault) { + if ($defaultConfigName -ne $choice) { + Write-Verbose "Setting config name '$choice' for domain '$($script:PSGSuite.Domain)' as default" + Set-PSGSuiteConfig -ConfigName $choice -SetAsDefaultConfig -Verbose:$false + $env:PSGSuiteDefaultDomain = $script:PSGSuite.Domain + [Environment]::SetEnvironmentVariable("PSGSuiteDefaultDomain", $script:PSGSuite.Domain, "User") + } + else { + Write-Warning "Config name '$choice' for domain '$($script:PSGSuite.Domain)' is already set to default --- no action taken" + } + } + } + else { + switch ($PSCmdlet.ParameterSetName) { + Domain { + Write-Warning "No config found for domain '$Domain'! Retaining existing config for domain '$($script:PSGSuite.Domain)'" + } + ConfigName { + Write-Warning "No config named '$ConfigName' found! Retaining existing config '$($script:PSGSuite.ConfigName)'" + } } } } diff --git a/PSGSuite/Public/Gmail/Get-GSGmailMessage.ps1 b/PSGSuite/Public/Gmail/Get-GSGmailMessage.ps1 index 057aab29..3a2f7947 100644 --- a/PSGSuite/Public/Gmail/Get-GSGmailMessage.ps1 +++ b/PSGSuite/Public/Gmail/Get-GSGmailMessage.ps1 @@ -61,7 +61,7 @@ function Get-GSGmailMessage { [string] $Format = "Full" ) - Begin { + Process { if ($User -ceq 'me') { $User = $Script:PSGSuite.AdminEmail } @@ -77,8 +77,6 @@ function Get-GSGmailMessage { User = $User } $service = New-GoogleService @serviceParams - } - Process { try { foreach ($mId in $Id) { $request = $service.Users.Messages.Get($User,$mId) diff --git a/PSGSuite/Public/Gmail/Restore-GSGmailMessage.ps1 b/PSGSuite/Public/Gmail/Restore-GSGmailMessage.ps1 index 92686c1b..a4fe7a5d 100644 --- a/PSGSuite/Public/Gmail/Restore-GSGmailMessage.ps1 +++ b/PSGSuite/Public/Gmail/Restore-GSGmailMessage.ps1 @@ -2,18 +2,18 @@ function Restore-GSGmailMessage { <# .SYNOPSIS Restores a trashed message to the inbox - + .DESCRIPTION Restores a trashed message to the inbox - + .PARAMETER User The primary email of the user to restore the message for Defaults to the AdminEmail user - + .PARAMETER Id The Id of the message to restore - + .EXAMPLE Restore-GSGmailMessage -User joe -Id 161622d7b76b7e1e,1616227c34d435f2 @@ -31,7 +31,7 @@ function Restore-GSGmailMessage { [String[]] $Id ) - Begin { + Process { if ($User -ceq 'me') { $User = $Script:PSGSuite.AdminEmail } @@ -44,8 +44,6 @@ function Restore-GSGmailMessage { User = $User } $service = New-GoogleService @serviceParams - } - Process { try { foreach ($mId in $Id) { $request = $service.Users.Messages.Untrash($User,$mId) @@ -62,4 +60,4 @@ function Restore-GSGmailMessage { } } } -} \ No newline at end of file +} diff --git a/PSGSuite/Public/Sheets/Update-GSSheet.ps1 b/PSGSuite/Public/Sheets/Update-GSSheet.ps1 new file mode 100644 index 00000000..1ed79651 --- /dev/null +++ b/PSGSuite/Public/Sheets/Update-GSSheet.ps1 @@ -0,0 +1,83 @@ +function Submit-GSSheetBatchUpdateRequest { + <# + .SYNOPSIS + Submits a batch update request to a Google Sheet + + .DESCRIPTION + Submits a batch update request to a Google Sheet + + .PARAMETER Title + The name of the SpreadSheet + + .PARAMETER User + The user to update the Sheet for + + .PARAMETER Launch + If $true, opens the SpreadSheet Url in your default browser + + .EXAMPLE + Update-GSSheet -Title "Finance Workbook" -Launch + + Creates a new SpreadSheet titled "Finance Workbook" and opens it in the browser on creation + #> + [OutputType('Google.Apis.Sheets.v4.Data.Spreadsheet')] + [cmdletbinding()] + Param + ( + [parameter(Mandatory = $true,ValueFromPipelineByPropertyName = $true)] + [Alias('SheetId')] + [String] + $Id, + [parameter(Mandatory = $false)] + [Alias('SheetTitle')] + [String] + $Title, + [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] + [Alias('Owner','PrimaryEmail','UserKey','Mail')] + [string] + $User = $Script:PSGSuite.AdminEmail, + [parameter(Mandatory = $false)] + [Alias('Open')] + [Switch] + $Launch + ) + Process { + if ($User -ceq 'me') { + $User = $Script:PSGSuite.AdminEmail + } + elseif ($User -notlike "*@*.*") { + $User = "$($User)@$($Script:PSGSuite.Domain)" + } + $serviceParams = @{ + Scope = 'https://www.googleapis.com/auth/drive' + ServiceType = 'Google.Apis.Sheets.v4.SheetsService' + User = $User + } + $service = New-GoogleService @serviceParams + try { + $body = New-Object 'Google.Apis.Sheets.v4.Data.Spreadsheet' + $body.Properties = New-Object 'Google.Apis.Sheets.v4.Data.SpreadsheetProperties' -Property @{ + Title = $Title + } + if (!$Title) { + $Title = "Untitled spreadsheet" + } + Write-Verbose "Creating Spreadsheet '$Title' for user '$User'" + $request = $service.Spreadsheets.Create($body) + $response = $request.Execute() | Add-Member -MemberType NoteProperty -Name 'User' -Value $User -PassThru + if ($Launch) { + Write-Verbose "Launching new spreadsheet at $($response.SpreadsheetUrl)" + Start-Process $response.SpreadsheetUrl + } + $response + } + catch { + if ($ErrorActionPreference -eq 'Stop') { + $PSCmdlet.ThrowTerminatingError($_) + } + else { + Write-Error $_ + } + } + } +} From c89c6519bff18d22ddd1636a33778f59ef964800 Mon Sep 17 00:00:00 2001 From: Nate Ferrell Date: Wed, 20 Mar 2019 01:14:38 -0500 Subject: [PATCH 2/2] pushing updates for v2.25.0 --- README.md | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 1c137bf6..f9193546 100644 --- a/README.md +++ b/README.md @@ -143,7 +143,18 @@ Update-GSSheetValue Export-GSSheet [Full CHANGELOG here](https://github.com/scrthq/PSGSuite/blob/master/CHANGELOG.md) -#### 2.24.0 - -* [Issue #159](https://github.com/scrthq/PSGSuite/issues/159) - * Added: `Revoke-GSStudentGuardianInvitation` to revoke student guardian invitations (Classroom API) +#### 2.25.0 + +* [Issue #162](https://github.com/scrthq/PSGSuite/issues/162) + * Updated: `New-GoogleService` now caches Service objects created during the current session. This means that repeated calls will attempt to use an existing Service object from the cache if present, otherwise it will create the Service as usual. + * Updated: `New-GoogleService` Verbose output. To cut down on verbose noisiness, the following verbose output is set: + * New Service created = `Building ServiceAccountCredential from....` + * First use of existing Service = `Using matching cached service for user....` + * Re-use of existing Service = No verbose output (helps cut down on pipeline verbosity where service re-use is expected) + * Added: `Get-PSGSuiteServiceCache` to get the current Service Cache for inspection. +* [Issue #163](https://github.com/scrthq/PSGSuite/issues/163) + * Added: `Get-GSCalendar` to get the CalendarList of a user. + * Added: `Remove-GSCalendarAcl` to remove Access Control List rules from Google Calendars. +* Miscellaneous + * Improved pipeline support for Gmail `*Message` functions and Calendar functions. + * Added tab completion to `Switch-PSGSuiteConfig` for the ConfigName parameter.