From f678ac95e494d8869f1ded919f197864024edde7 Mon Sep 17 00:00:00 2001 From: Foggy2 <23325075+Foggy2@users.noreply.github.com> Date: Sat, 7 Jun 2025 12:07:52 +0930 Subject: [PATCH 01/13] Added programmatic generation capabilities to the module build process --- CHANGELOG.md | 12 ++ PSGSuite/Module/Aliases.ps1 | 59 ++++++ PSGSuite/Module/Initialization.ps1 | 110 +++++++++++ PSGSuite/Private/Import-GoogleSDK.ps1 | 5 +- build.ps1 | 4 +- ci/templates/Module/Aliases.ps1 | 20 ++ ci/templates/README.md | 80 ++++++++ psake.ps1 | 261 ++++++++++++-------------- 8 files changed, 406 insertions(+), 145 deletions(-) create mode 100644 PSGSuite/Module/Aliases.ps1 create mode 100644 PSGSuite/Module/Initialization.ps1 create mode 100644 ci/templates/Module/Aliases.ps1 create mode 100644 ci/templates/README.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 9fe19f03..60e8fb38 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -115,6 +115,18 @@ # PSGSuite - ChangeLog +## 3.x.x - 2025-xx-xx + +- Added `-lib` parameter to `Import-GoogleSDK` that defines the path of the directory containing the Google API libraries. +- Added functionality to programmatically generate module components during the module build process. Further details can be found at [ci\templates\README.md](ci\templates\README.md). +- Added two additional tasks `Download` and `Generate` to the module build process. + - `Download` task performs the downloading of NuGet dependencies which was previously performed by the `Compile` task. + - `Generate` task performs the programmatic generation of module content. +- Changed the `Compile` task of the module build process to support including content from the `Module` and `Classes` directories in the compiled `psgsuite.psm1` file. Content from the module source directories will be compiled into the module in the following order; `Class`, `Private`, `Public`, `Module` +- Moved the existing module initialization code out of the `Compile` task of the module build process and split into two parts: + - The dynamic alias logic has been moved into the `templates\Module\Aliases.ps1` template file. + - The static module initialization logic has been moved into the `Module\Initialization.ps1` file. + ## 3.0.0 - 2024-11-20 ### Breaking Changes diff --git a/PSGSuite/Module/Aliases.ps1 b/PSGSuite/Module/Aliases.ps1 new file mode 100644 index 00000000..482ad895 --- /dev/null +++ b/PSGSuite/Module/Aliases.ps1 @@ -0,0 +1,59 @@ +# Programmatically generated from template 'Module\Aliases.ps1' +# This file will be overwritten during the module build process. + +$aliasHash = # Alias => => => => => => => => Function +@{ + 'Add-GSDriveFilePermissions' = 'Add-GSDrivePermission' + 'Export-PSGSuiteConfiguration' = 'Set-PSGSuiteConfig' + 'Get-GSCalendarEventList' = 'Get-GSCalendarEvent' + 'Get-GSCalendarResourceList' = 'Get-GSResourceList' + 'Get-GSDataTransferApplicationList' = 'Get-GSDataTransferApplication' + 'Get-GSDriveFileInfo' = 'Get-GSDriveFile' + 'Get-GSDriveFilePermissionsList' = 'Get-GSDrivePermission' + 'Get-GSGmailDelegates' = 'Get-GSGmailDelegate' + 'Get-GSGmailFilterList' = 'Get-GSGmailFilter' + 'Get-GSGmailLabelList' = 'Get-GSGmailLabel' + 'Get-GSGmailMessageInfo' = 'Get-GSGmailMessage' + 'Get-GSGmailSendAsSettings' = 'Get-GSGmailSendAsAlias' + 'Get-GSGmailSignature' = 'Get-GSGmailSendAsAlias' + 'Get-GSGroupList' = 'Get-GSGroup' + 'Get-GSGroupMemberList' = 'Get-GSGroupMember' + 'Get-GSMobileDeviceList' = 'Get-GSMobileDevice' + 'Get-GSOrganizationalUnitList' = 'Get-GSOrganizationalUnit' + 'Get-GSOrgUnit' = 'Get-GSOrganizationalUnit' + 'Get-GSOrgUnitList' = 'Get-GSOrganizationalUnit' + 'Get-GSOU' = 'Get-GSOrganizationalUnit' + 'Get-GSResourceList' = 'Get-GSResource' + 'Get-GSTeamDrive' = 'Get-GSDrive' + 'Get-GSTeamDrivesList' = 'Get-GSDrive' + 'Get-GSUserASPList' = 'Get-GSUserASP' + 'Get-GSUserLicenseInfo' = 'Get-GSUserLicense' + 'Get-GSUserLicenseList' = 'Get-GSUserLicense' + 'Get-GSUserList' = 'Get-GSUser' + 'Get-GSUserSchemaInfo' = 'Get-GSUserSchema' + 'Get-GSUserSchemaList' = 'Get-GSUserSchema' + 'Get-GSUserTokenList' = 'Get-GSUserToken' + 'Import-PSGSuiteConfiguration' = 'Get-PSGSuiteConfig' + 'Move-GSGmailMessageToTrash' = 'Remove-GSGmailMessage' + 'New-GSCalendarResource' = 'New-GSResource' + 'New-GSTeamDrive' = 'New-GSDrive' + 'Remove-GSGmailMessageFromTrash' = 'Restore-GSGmailMessage' + 'Remove-GSTeamDrive' = 'Remove-GSDrive' + 'Set-PSGSuiteDefaultDomain' = 'Switch-PSGSuiteConfig' + 'Switch-PSGSuiteDomain' = 'Switch-PSGSuiteConfig' + 'Update-GSCalendarResource' = 'Update-GSResource' + 'Update-GSGmailSendAsSettings' = 'Update-GSGmailSendAsAlias' + 'Update-GSSheetValue' = 'Export-GSSheet' + 'Update-GSTeamDrive' = 'Update-GSDrive' +} + +foreach ($key in $aliasHash.Keys) { + try { + New-Alias -Name $key -Value $aliasHash[$key] -Force + } + catch { + Write-Error "[ALIAS: $($key)] $($_.Exception.Message.ToString())" + } +} + +Export-ModuleMember -Alias '*' diff --git a/PSGSuite/Module/Initialization.ps1 b/PSGSuite/Module/Initialization.ps1 new file mode 100644 index 00000000..fa81a090 --- /dev/null +++ b/PSGSuite/Module/Initialization.ps1 @@ -0,0 +1,110 @@ +Import-GoogleSDK + +if ($global:PSGSuiteKey -and $MyInvocation.BoundParameters['Debug']) { + $prevDebugPref = $DebugPreference + $DebugPreference = "Continue" + Write-Debug "`$global:PSGSuiteKey is set to a $($global:PSGSuiteKey.Count * 8)-bit key!" + $DebugPreference = $prevDebugPref +} + +if (!(Test-Path (Join-Path "~" ".scrthq"))) { + New-Item -Path (Join-Path "~" ".scrthq") -ItemType Directory -Force | Out-Null +} + +if ($PSVersionTable.ContainsKey('PSEdition') -and $PSVersionTable.PSEdition -eq 'Core' -and !$Global:PSGSuiteKey -and !$IsWindows) { + if (!(Test-Path (Join-Path (Join-Path "~" ".scrthq") "BlockCoreCLREncryptionWarning.txt"))) { + Write-Warning "CoreCLR does not support DPAPI encryption! Setting a basic AES key to prevent errors. Please create a unique key as soon as possible as this will only obfuscate secrets from plain text in the Configuration, the key is not secure as is. If you would like to prevent this message from displaying in the future, run the following command:`n`nBlock-CoreCLREncryptionWarning`n" + } + $Global:PSGSuiteKey = [Byte[]]@(1..16) + $ConfigScope = "User" +} + +if ($Global:PSGSuiteKey -is [System.Security.SecureString]) { + $Method = "SecureString" + if (!$ConfigScope) { + $ConfigScope = "Machine" + } +} +elseif ($Global:PSGSuiteKey -is [System.Byte[]]) { + $Method = "AES Key" + if (!$ConfigScope) { + $ConfigScope = "Machine" + } +} +else { + $Method = "DPAPI" + $ConfigScope = "User" +} + +Add-MetadataConverter -Converters @{ + [SecureString] = { + $encParams = @{} + if ($Global:PSGSuiteKey -is [System.Byte[]]) { + $encParams["Key"] = $Global:PSGSuiteKey + } + elseif ($Global:PSGSuiteKey -is [System.Security.SecureString]) { + $encParams["SecureKey"] = $Global:PSGSuiteKey + } + 'ConvertTo-SecureString "{0}"' -f (ConvertFrom-SecureString $_ @encParams) + } + "Secure" = { + param([string]$String) + $encParams = @{} + if ($Global:PSGSuiteKey -is [System.Byte[]]) { + $encParams["Key"] = $Global:PSGSuiteKey + } + elseif ($Global:PSGSuiteKey -is [System.Security.SecureString]) { + $encParams["SecureKey"] = $Global:PSGSuiteKey + } + ConvertTo-SecureString $String @encParams + } + "ConvertTo-SecureString" = { + param([string]$String) + $encParams = @{} + if ($Global:PSGSuiteKey -is [System.Byte[]]) { + $encParams["Key"] = $Global:PSGSuiteKey + } + elseif ($Global:PSGSuiteKey -is [System.Security.SecureString]) { + $encParams["SecureKey"] = $Global:PSGSuiteKey + } + ConvertTo-SecureString $String @encParams + } +} + +try { + $confParams = @{ + Scope = $ConfigScope + } + if ($ConfigName) { + $confParams["ConfigName"] = $ConfigName + $Script:ConfigName = $ConfigName + } + try { + if ($global:PSGSuite) { + Write-Warning "Using config $(if ($global:PSGSuite.ConfigName){"name '$($global:PSGSuite.ConfigName)' "})found in variable: `$global:PSGSuite" + Write-Verbose "$(($global:PSGSuite | Format-List | Out-String).Trim())" + if ($global:PSGSuite -is [System.Collections.Hashtable]) { + $global:PSGSuite = New-Object PSObject -Property $global:PSGSuite + } + $script:PSGSuite = $global:PSGSuite + } + else { + Get-PSGSuiteConfig @confParams -ErrorAction Stop + } + } + catch { + if (Test-Path "$ModuleRoot\$env:USERNAME-$env:COMPUTERNAME-$env:PSGSuiteDefaultDomain-PSGSuite.xml") { + Get-PSGSuiteConfig -Path "$ModuleRoot\$env:USERNAME-$env:COMPUTERNAME-$env:PSGSuiteDefaultDomain-PSGSuite.xml" -ErrorAction Stop + Write-Warning "No Configuration.psd1 found at scope '$ConfigScope'; falling back to legacy XML. If you would like to convert your legacy XML to the newer Configuration.psd1, run the following command: + +Get-PSGSuiteConfig -Path '$ModuleRoot\$env:USERNAME-$env:COMPUTERNAME-$env:PSGSuiteDefaultDomain-PSGSuite.xml' -PassThru | Set-PSGSuiteConfig +" + } + else { + Write-Warning "There was no config returned! Please make sure you are using the correct key or have a configuration already saved." + } + } +} +catch { + Write-Warning "There was no config returned! Please make sure you are using the correct key or have a configuration already saved." +} \ No newline at end of file diff --git a/PSGSuite/Private/Import-GoogleSDK.ps1 b/PSGSuite/Private/Import-GoogleSDK.ps1 index 259f39d8..d95198f4 100644 --- a/PSGSuite/Private/Import-GoogleSDK.ps1 +++ b/PSGSuite/Private/Import-GoogleSDK.ps1 @@ -1,8 +1,9 @@ function Import-GoogleSDK { [CmdletBinding()] - Param() + Param( + [string]$lib = (Resolve-Path "$($script:ModuleRoot)\lib") + ) Process { - $lib = Resolve-Path "$($script:ModuleRoot)\lib" $refs = @() $sdkPath = $lib $dlls = Get-ChildItem $sdkPath -Filter "*.dll" diff --git a/build.ps1 b/build.ps1 index 47f3ff86..02e8b045 100644 --- a/build.ps1 +++ b/build.ps1 @@ -1,9 +1,9 @@ [cmdletbinding()] param( [parameter( Position = 0)] - [ValidateSet('Init','Clean','Compile','Import','Test','Full','Deploy','Skip','Docs')] + [ValidateSet('Init','Clean', 'Download', 'Generate', 'Compile','Import','Test','Full','Deploy','Skip','Docs')] [string[]] - $Task = @('Init','Clean','Compile','Import'), + $Task = @('Init','Clean', 'Download', 'Generate', 'Compile','Import'), [parameter()] [Alias('nr','nor')] [switch]$NoRestore, diff --git a/ci/templates/Module/Aliases.ps1 b/ci/templates/Module/Aliases.ps1 new file mode 100644 index 00000000..c7c313a3 --- /dev/null +++ b/ci/templates/Module/Aliases.ps1 @@ -0,0 +1,20 @@ +Param( + [parameter(mandatory=$true)]$SourceDirectory +) + +$aliasHashContents = (Get-Content (Join-Path $SourceDirectory "PSGSuite" "Aliases" "PSGSuite.Aliases.ps1") -Raw).Trim() + +@" +`$aliasHash = $aliasHashContents + +foreach (`$key in `$aliasHash.Keys) { + try { + New-Alias -Name `$key -Value `$aliasHash[`$key] -Force + } + catch { + Write-Error "[ALIAS: `$(`$key)] `$(`$_.Exception.Message.ToString())" + } +} + +Export-ModuleMember -Alias '*' +"@ \ No newline at end of file diff --git a/ci/templates/README.md b/ci/templates/README.md new file mode 100644 index 00000000..7cc74ce4 --- /dev/null +++ b/ci/templates/README.md @@ -0,0 +1,80 @@ +# Generation Templates + +This directory contains the templates used to programmatically generate module content during the build process. Each template is an independent `.ps1` file that outputs the generated content to the PowerShell pipeline as either a string or a hashtable of strings. + +## Template Directories + +By default the template directory tree mirrors the module source directory tree. When executed, all output will be written to the corresponding directory found in the module source directory. + +For example, the template `\Templates\Public\Authentication\Foo.ps1` will have it's output written to the directory `\PSGSuite\Public\Authentication\`. + +## Template Priority + +It may be beneficial to prioritise the order of execution for each template. It is possible to assign a priority value to each template between `1` execute first and `9` execute last. To assign a value, prefix the template name with `-`. + +If a priority value is not assigned to a template, it will be assigned priority `5` by default. + +For example, for the following templates: + +- `2-foo.ps1` is assigned priority `2` +- `bar.ps1` is assigned priority `5` + +## Template Output + +**String Output** + +When a template returns a string value it's output filename will be the template file name excluding any priority prefix. + +For example, the file `\Templates\Public\Authentication\2-Foo.ps1` will have it's output written to the file `\PSGSuite\Public\Authentication\Foo.ps1`. + +**Hashtable Output** + +When a template returns a hashtable, the value for each entry will be converted to a string and the key becomes the output file name including the file extension. + +For example, the file `\Templates\Public\Authentication\Foo.ps1` produces the following hashtable. + +``` +@{ + 'Get-Foo.ps1' = 'Write-Host "Got Foo!"' + 'Remove-Foo.ps1' = 'Write-Host "Removed Foo!"' +} +``` + +Two output files would be produced. + +- `\PSGSuite\Public\Authentication\Get-Foo.ps1` +- `\PSGSuite\Public\Authentication\Remove-Foo.ps1` + +So far the output files have been relative to the location of the template file. It is also possible to provide an absolute file path, relative to the source directory, as the entries key. When this is done, the output file path will be the specified absolute path. + +For example, the file `\Templates\Public\Authentication\Foo.ps1` produces the following hashtable. + +``` +@{ + '\Private\Authentication\Get-Foo.ps1' = 'Write-Host "Got Foo!"' + '\Show-Foo.ps1' = 'Write-Host "Show Foo!"' + 'Remove-Foo.ps1' = 'Write-Host "Removed Foo!"' +} +``` + +Three output files would be produced. + +- `\PSGSuite\Private\Authentication\Get-Foo.ps1` +- `\PSGSuite\Show-Foo.ps1` +- `\PSGSuite\Public\Authentication\Remove-Foo.ps1` + + +**Unsupported Output** + +When a template returns an unsupported type, it will be converted and processed as a string. + + +## Template Execution + +Template execution is triggered by the `generate` task of the PSGSuite build pipeline. + +The following parameters are passed to the template: + +- `-ModuleSource` - This is the file path to the repository root. + +All generated output files will each have a comment added at the top of the file indicating it was programmatically generated. diff --git a/psake.ps1 b/psake.ps1 index 81eb97e4..32715ed4 100644 --- a/psake.ps1 +++ b/psake.ps1 @@ -86,10 +86,127 @@ Task Clean -Depends Init { " Cleaned previous output directory [$outputDir]" } -Description 'Cleans module output directory' -Task Compile -Depends Clean { + +Task Download -Depends Clean { + if ("$env:NoNugetRestore" -ne 'True') { + New-Item -Path "$outputModVerDir\lib" -ItemType Directory -ErrorAction SilentlyContinue | Out-Null + Write-BuildLog "Installing NuGet dependencies..." + Install-NuGetDependencies -Destination $outputModVerDir -AddlSearchString $NuGetSearchStrings -Verbose + } + else { + Write-BuildLog "Skipping NuGet Restore due to `$env:NoNugetRestore = '$env:NoNugetRestore'" + } +} -Description 'Downloads module third-party dependencies' + +Task Generate -Depends Clean, Download { + # Load the Google SDKs for use during dynamic content generation + Write-BuildLog "Importing the Google SDK" + . (Join-Path $Sut 'Private\Import-GoogleSDK.ps1') + Import-GoogleSDK -Lib (Join-Path $outputModVerDir 'lib') + + # Dynamically generate module content by executing all .ps1 files in the template folder. + # Each template is expected to output a string value that will be saved in the corresponding file in the source folder. + # Child scope is used to minimise interference and pollution of build variables. + $TemplatesPath = Join-Path $PSScriptRoot "ci" "templates" + $SourceDirectory = $Sut + + $DefaultPriority = 5 + $TemplatePriorities = @{ + 1 = @() + 2 = @() + 3 = @() + 4 = @() + 5 = @() + 6 = @() + 7 = @() + 8 = @() + 9 = @() + } + Get-ChildItem -Path $TemplatesPath -Filter "*.ps1" -Recurse -File | ForEach-Object { + If ($_.Name -match "^(?[1-9])-"){ + $TemplatePriorities[[int]$Matches.priority] += $_ + } else { + $TemplatePriorities[$DefaultPriority] += $_ + } + } + + $ExecutionOrder = @() + ForEach ($Priority in @(1..9)){ + $TemplatePriorities[$Priority] = $TemplatePriorities[$Priority] | Sort-Object + $TemplatePriorities[$Priority] | ForEach-Object { + $ExecutionOrder += $_ + } + } + + $ExecutionOrder | ForEach-Object { + + $RelativeTemplatePath = $_.FullName.Substring($TemplatesPath.Length + 1) + $RelativeDirectory = $_.DirectoryName.Substring($TemplatesPath.Length + 1) + + Write-BuildLog "Executing template: $RelativeTemplatePath" + $TemplateResult = & $_.FullName -SourceDirectory $PSScriptRoot + + If ($TemplateResult){ + + $OutputDirectory = Join-Path $SourceDirectory $RelativeDirectory + + If ($TemplateResult -is [hashtable]){ + + ForEach ($key in $TemplateResult.keys){ + + If ($Key -match '^[\\/]'){ + $OutputPath = Join-Path $SourceDirectory $Key + } else { + $OutputPath = Join-Path $OutputDirectory $(Split-Path $Key -Leaf) + } + + if (-not (Test-Path (Split-Path $OutputPath -Parent))){ + New-Item -Path (Split-Path $OutputPath -Parent) -ItemType Directory -Force | Out-Null + } + + $OutputValue = $TemplateResult[$Key] + @" +# Programmatically generated from template '$RelativeTemplatePath' +# This file will be overwritten during the module build process. + +$OutputValue +"@ | Out-File -Path $OutputPath -Encoding UTF8 -Force + Write-BuildLog "Template output written to: $OutputPath" + + } + + } else { + If ($_.Name -Match "^[1-9]-"){ + $OutputPath = Join-Path $OutputDirectory $_.Name.Substring(2) + } else { + $OutputPath = Join-Path $OutputDirectory $_.Name + } + + if (-not (Test-Path (Split-Path $OutputPath -Parent))){ + New-Item -Path (Split-Path $OutputPath -Parent) -ItemType Directory -Force | Out-Null + } + + @" +# Programmatically generated from template '$RelativeTemplatePath' +# This file will be overwritten during the module build process. + +$TemplateResult +"@ | Out-File -Path $OutputPath -Encoding UTF8 -Force + Write-BuildLog "Template output written to: $OutputPath" + + } + + } else { + Write-BuildLog "Template did not output any content" -Severe + } + + Write-BuildLog "Completed template: $RelativeTemplatePath" + } +} -Description "Generates module content from template files" + +Task Compile -Depends Clean, Download, Generate { # Create module output directory $functionsToExport = @() - $sutLib = [System.IO.Path]::Combine($sut, 'lib') $aliasesToExport = (. $sut\Aliases\PSGSuite.Aliases.ps1).Keys if (-not (Test-Path $outputModVerDir)) { $modDir = New-Item -Path $outputModDir -ItemType Directory -ErrorAction SilentlyContinue @@ -100,7 +217,7 @@ Task Compile -Depends Clean { Write-BuildLog 'Creating psm1...' $psm1 = Copy-Item -Path (Join-Path -Path $sut -ChildPath 'PSGSuite.psm1') -Destination (Join-Path -Path $outputModVerDir -ChildPath "$($ENV:BHProjectName).psm1") -PassThru - foreach ($scope in @('Private', 'Public')) { + foreach ($scope in @('Class', 'Private', 'Public', 'Module')) { Write-BuildLog "Copying contents from files in source folder to PSM1: $($scope)" $gciPath = Join-Path $sut $scope if (Test-Path $gciPath) { @@ -115,146 +232,8 @@ Task Compile -Depends Clean { } } - Invoke-CommandWithLog { Remove-Module $env:BHProjectName -ErrorAction SilentlyContinue -Force -Verbose:$false } - if ("$env:NoNugetRestore" -ne 'True') { - New-Item -Path "$outputModVerDir\lib" -ItemType Directory -ErrorAction SilentlyContinue | Out-Null - Write-BuildLog "Installing NuGet dependencies..." - Install-NuGetDependencies -Destination $outputModVerDir -AddlSearchString $NuGetSearchStrings -Verbose - } - else { - Write-BuildLog "Skipping NuGet Restore due to `$env:NoNugetRestore = '$env:NoNugetRestore'" - } - - $aliasHashContents = (Get-Content "$sut\Aliases\PSGSuite.Aliases.ps1" -Raw).Trim() - - # Set remainder of PSM1 contents - @" - -Import-GoogleSDK - -if (`$global:PSGSuiteKey -and `$MyInvocation.BoundParameters['Debug']) { - `$prevDebugPref = `$DebugPreference - `$DebugPreference = "Continue" - Write-Debug "```$global:PSGSuiteKey is set to a `$(`$global:PSGSuiteKey.Count * 8)-bit key!" - `$DebugPreference = `$prevDebugPref -} - -`$aliasHash = $aliasHashContents - -foreach (`$key in `$aliasHash.Keys) { - try { - New-Alias -Name `$key -Value `$aliasHash[`$key] -Force - } - catch { - Write-Error "[ALIAS: `$(`$key)] `$(`$_.Exception.Message.ToString())" - } -} - -Export-ModuleMember -Alias '*' - -if (!(Test-Path (Join-Path "~" ".scrthq"))) { - New-Item -Path (Join-Path "~" ".scrthq") -ItemType Directory -Force | Out-Null -} - -if (`$PSVersionTable.ContainsKey('PSEdition') -and `$PSVersionTable.PSEdition -eq 'Core' -and !`$Global:PSGSuiteKey -and !`$IsWindows) { - if (!(Test-Path (Join-Path (Join-Path "~" ".scrthq") "BlockCoreCLREncryptionWarning.txt"))) { - Write-Warning "CoreCLR does not support DPAPI encryption! Setting a basic AES key to prevent errors. Please create a unique key as soon as possible as this will only obfuscate secrets from plain text in the Configuration, the key is not secure as is. If you would like to prevent this message from displaying in the future, run the following command:`n`nBlock-CoreCLREncryptionWarning`n" - } - `$Global:PSGSuiteKey = [Byte[]]@(1..16) - `$ConfigScope = "User" -} - -if (`$Global:PSGSuiteKey -is [System.Security.SecureString]) { - `$Method = "SecureString" - if (!`$ConfigScope) { - `$ConfigScope = "Machine" - } -} -elseif (`$Global:PSGSuiteKey -is [System.Byte[]]) { - `$Method = "AES Key" - if (!`$ConfigScope) { - `$ConfigScope = "Machine" - } -} -else { - `$Method = "DPAPI" - `$ConfigScope = "User" -} - -Add-MetadataConverter -Converters @{ - [SecureString] = { - `$encParams = @{} - if (`$Global:PSGSuiteKey -is [System.Byte[]]) { - `$encParams["Key"] = `$Global:PSGSuiteKey - } - elseif (`$Global:PSGSuiteKey -is [System.Security.SecureString]) { - `$encParams["SecureKey"] = `$Global:PSGSuiteKey - } - 'ConvertTo-SecureString "{0}"' -f (ConvertFrom-SecureString `$_ @encParams) - } - "Secure" = { - param([string]`$String) - `$encParams = @{} - if (`$Global:PSGSuiteKey -is [System.Byte[]]) { - `$encParams["Key"] = `$Global:PSGSuiteKey - } - elseif (`$Global:PSGSuiteKey -is [System.Security.SecureString]) { - `$encParams["SecureKey"] = `$Global:PSGSuiteKey - } - ConvertTo-SecureString `$String @encParams - } - "ConvertTo-SecureString" = { - param([string]`$String) - `$encParams = @{} - if (`$Global:PSGSuiteKey -is [System.Byte[]]) { - `$encParams["Key"] = `$Global:PSGSuiteKey - } - elseif (`$Global:PSGSuiteKey -is [System.Security.SecureString]) { - `$encParams["SecureKey"] = `$Global:PSGSuiteKey - } - ConvertTo-SecureString `$String @encParams - } -} - -try { - `$confParams = @{ - Scope = `$ConfigScope - } - if (`$ConfigName) { - `$confParams["ConfigName"] = `$ConfigName - `$Script:ConfigName = `$ConfigName - } - try { - if (`$global:PSGSuite) { - Write-Warning "Using config `$(if (`$global:PSGSuite.ConfigName){"name '`$(`$global:PSGSuite.ConfigName)' "})found in variable: ```$global:PSGSuite" - Write-Verbose "`$((`$global:PSGSuite | Format-List | Out-String).Trim())" - if (`$global:PSGSuite -is [System.Collections.Hashtable]) { - `$global:PSGSuite = New-Object PSObject -Property `$global:PSGSuite - } - `$script:PSGSuite = `$global:PSGSuite - } - else { - Get-PSGSuiteConfig @confParams -ErrorAction Stop - } - } - catch { - if (Test-Path "`$ModuleRoot\`$env:USERNAME-`$env:COMPUTERNAME-`$env:PSGSuiteDefaultDomain-PSGSuite.xml") { - Get-PSGSuiteConfig -Path "`$ModuleRoot\`$env:USERNAME-`$env:COMPUTERNAME-`$env:PSGSuiteDefaultDomain-PSGSuite.xml" -ErrorAction Stop - Write-Warning "No Configuration.psd1 found at scope '`$ConfigScope'; falling back to legacy XML. If you would like to convert your legacy XML to the newer Configuration.psd1, run the following command:`n`nGet-PSGSuiteConfig -Path '`$ModuleRoot\`$env:USERNAME-`$env:COMPUTERNAME-`$env:PSGSuiteDefaultDomain-PSGSuite.xml' -PassThru | Set-PSGSuiteConfig`n" - } - else { - Write-Warning "There was no config returned! Please make sure you are using the correct key or have a configuration already saved." - } - } -} -catch { - Write-Warning "There was no config returned! Please make sure you are using the correct key or have a configuration already saved." -} - -"@ | Add-Content -Path $psm1 -Encoding UTF8 - # Copy over manifest Copy-Item -Path $env:BHPSModuleManifest -Destination $outputModVerDir From b7102cefcb22c0061a73171acc106e1d4dcd6b95 Mon Sep 17 00:00:00 2001 From: Foggy2 <23325075+Foggy2@users.noreply.github.com> Date: Sat, 7 Jun 2025 14:34:51 +0930 Subject: [PATCH 02/13] Fixed path handling for templates in the template root directory. --- psake.ps1 | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/psake.ps1 b/psake.ps1 index 32715ed4..d72bad12 100644 --- a/psake.ps1 +++ b/psake.ps1 @@ -141,7 +141,10 @@ Task Generate -Depends Clean, Download { $ExecutionOrder | ForEach-Object { $RelativeTemplatePath = $_.FullName.Substring($TemplatesPath.Length + 1) - $RelativeDirectory = $_.DirectoryName.Substring($TemplatesPath.Length + 1) + $RelativeDirectory = $_.DirectoryName.Substring($TemplatesPath.Length) + If ($RelativeDirectory -match '^[\\/]'){ + $RelativeDirectory = $RelativeDirectory.Substring(1) + } Write-BuildLog "Executing template: $RelativeTemplatePath" $TemplateResult = & $_.FullName -SourceDirectory $PSScriptRoot From 61aa9dffc244f3baa8020834ab495f4f6209cd3b Mon Sep 17 00:00:00 2001 From: Foggy2 <23325075+Foggy2@users.noreply.github.com> Date: Mon, 16 Jun 2025 19:45:57 +0930 Subject: [PATCH 03/13] Added programmatic generation capabilities to the module build process --- CHANGELOG.md | 12 ++ PSGSuite/Module/Aliases.ps1 | 59 ++++++ PSGSuite/Module/Initialization.ps1 | 110 +++++++++++ PSGSuite/Private/Import-GoogleSDK.ps1 | 5 +- build.ps1 | 4 +- ci/templates/Module/Aliases.ps1 | 20 ++ ci/templates/README.md | 80 ++++++++ psake.ps1 | 261 ++++++++++++-------------- 8 files changed, 406 insertions(+), 145 deletions(-) create mode 100644 PSGSuite/Module/Aliases.ps1 create mode 100644 PSGSuite/Module/Initialization.ps1 create mode 100644 ci/templates/Module/Aliases.ps1 create mode 100644 ci/templates/README.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 9fe19f03..60e8fb38 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -115,6 +115,18 @@ # PSGSuite - ChangeLog +## 3.x.x - 2025-xx-xx + +- Added `-lib` parameter to `Import-GoogleSDK` that defines the path of the directory containing the Google API libraries. +- Added functionality to programmatically generate module components during the module build process. Further details can be found at [ci\templates\README.md](ci\templates\README.md). +- Added two additional tasks `Download` and `Generate` to the module build process. + - `Download` task performs the downloading of NuGet dependencies which was previously performed by the `Compile` task. + - `Generate` task performs the programmatic generation of module content. +- Changed the `Compile` task of the module build process to support including content from the `Module` and `Classes` directories in the compiled `psgsuite.psm1` file. Content from the module source directories will be compiled into the module in the following order; `Class`, `Private`, `Public`, `Module` +- Moved the existing module initialization code out of the `Compile` task of the module build process and split into two parts: + - The dynamic alias logic has been moved into the `templates\Module\Aliases.ps1` template file. + - The static module initialization logic has been moved into the `Module\Initialization.ps1` file. + ## 3.0.0 - 2024-11-20 ### Breaking Changes diff --git a/PSGSuite/Module/Aliases.ps1 b/PSGSuite/Module/Aliases.ps1 new file mode 100644 index 00000000..482ad895 --- /dev/null +++ b/PSGSuite/Module/Aliases.ps1 @@ -0,0 +1,59 @@ +# Programmatically generated from template 'Module\Aliases.ps1' +# This file will be overwritten during the module build process. + +$aliasHash = # Alias => => => => => => => => Function +@{ + 'Add-GSDriveFilePermissions' = 'Add-GSDrivePermission' + 'Export-PSGSuiteConfiguration' = 'Set-PSGSuiteConfig' + 'Get-GSCalendarEventList' = 'Get-GSCalendarEvent' + 'Get-GSCalendarResourceList' = 'Get-GSResourceList' + 'Get-GSDataTransferApplicationList' = 'Get-GSDataTransferApplication' + 'Get-GSDriveFileInfo' = 'Get-GSDriveFile' + 'Get-GSDriveFilePermissionsList' = 'Get-GSDrivePermission' + 'Get-GSGmailDelegates' = 'Get-GSGmailDelegate' + 'Get-GSGmailFilterList' = 'Get-GSGmailFilter' + 'Get-GSGmailLabelList' = 'Get-GSGmailLabel' + 'Get-GSGmailMessageInfo' = 'Get-GSGmailMessage' + 'Get-GSGmailSendAsSettings' = 'Get-GSGmailSendAsAlias' + 'Get-GSGmailSignature' = 'Get-GSGmailSendAsAlias' + 'Get-GSGroupList' = 'Get-GSGroup' + 'Get-GSGroupMemberList' = 'Get-GSGroupMember' + 'Get-GSMobileDeviceList' = 'Get-GSMobileDevice' + 'Get-GSOrganizationalUnitList' = 'Get-GSOrganizationalUnit' + 'Get-GSOrgUnit' = 'Get-GSOrganizationalUnit' + 'Get-GSOrgUnitList' = 'Get-GSOrganizationalUnit' + 'Get-GSOU' = 'Get-GSOrganizationalUnit' + 'Get-GSResourceList' = 'Get-GSResource' + 'Get-GSTeamDrive' = 'Get-GSDrive' + 'Get-GSTeamDrivesList' = 'Get-GSDrive' + 'Get-GSUserASPList' = 'Get-GSUserASP' + 'Get-GSUserLicenseInfo' = 'Get-GSUserLicense' + 'Get-GSUserLicenseList' = 'Get-GSUserLicense' + 'Get-GSUserList' = 'Get-GSUser' + 'Get-GSUserSchemaInfo' = 'Get-GSUserSchema' + 'Get-GSUserSchemaList' = 'Get-GSUserSchema' + 'Get-GSUserTokenList' = 'Get-GSUserToken' + 'Import-PSGSuiteConfiguration' = 'Get-PSGSuiteConfig' + 'Move-GSGmailMessageToTrash' = 'Remove-GSGmailMessage' + 'New-GSCalendarResource' = 'New-GSResource' + 'New-GSTeamDrive' = 'New-GSDrive' + 'Remove-GSGmailMessageFromTrash' = 'Restore-GSGmailMessage' + 'Remove-GSTeamDrive' = 'Remove-GSDrive' + 'Set-PSGSuiteDefaultDomain' = 'Switch-PSGSuiteConfig' + 'Switch-PSGSuiteDomain' = 'Switch-PSGSuiteConfig' + 'Update-GSCalendarResource' = 'Update-GSResource' + 'Update-GSGmailSendAsSettings' = 'Update-GSGmailSendAsAlias' + 'Update-GSSheetValue' = 'Export-GSSheet' + 'Update-GSTeamDrive' = 'Update-GSDrive' +} + +foreach ($key in $aliasHash.Keys) { + try { + New-Alias -Name $key -Value $aliasHash[$key] -Force + } + catch { + Write-Error "[ALIAS: $($key)] $($_.Exception.Message.ToString())" + } +} + +Export-ModuleMember -Alias '*' diff --git a/PSGSuite/Module/Initialization.ps1 b/PSGSuite/Module/Initialization.ps1 new file mode 100644 index 00000000..fa81a090 --- /dev/null +++ b/PSGSuite/Module/Initialization.ps1 @@ -0,0 +1,110 @@ +Import-GoogleSDK + +if ($global:PSGSuiteKey -and $MyInvocation.BoundParameters['Debug']) { + $prevDebugPref = $DebugPreference + $DebugPreference = "Continue" + Write-Debug "`$global:PSGSuiteKey is set to a $($global:PSGSuiteKey.Count * 8)-bit key!" + $DebugPreference = $prevDebugPref +} + +if (!(Test-Path (Join-Path "~" ".scrthq"))) { + New-Item -Path (Join-Path "~" ".scrthq") -ItemType Directory -Force | Out-Null +} + +if ($PSVersionTable.ContainsKey('PSEdition') -and $PSVersionTable.PSEdition -eq 'Core' -and !$Global:PSGSuiteKey -and !$IsWindows) { + if (!(Test-Path (Join-Path (Join-Path "~" ".scrthq") "BlockCoreCLREncryptionWarning.txt"))) { + Write-Warning "CoreCLR does not support DPAPI encryption! Setting a basic AES key to prevent errors. Please create a unique key as soon as possible as this will only obfuscate secrets from plain text in the Configuration, the key is not secure as is. If you would like to prevent this message from displaying in the future, run the following command:`n`nBlock-CoreCLREncryptionWarning`n" + } + $Global:PSGSuiteKey = [Byte[]]@(1..16) + $ConfigScope = "User" +} + +if ($Global:PSGSuiteKey -is [System.Security.SecureString]) { + $Method = "SecureString" + if (!$ConfigScope) { + $ConfigScope = "Machine" + } +} +elseif ($Global:PSGSuiteKey -is [System.Byte[]]) { + $Method = "AES Key" + if (!$ConfigScope) { + $ConfigScope = "Machine" + } +} +else { + $Method = "DPAPI" + $ConfigScope = "User" +} + +Add-MetadataConverter -Converters @{ + [SecureString] = { + $encParams = @{} + if ($Global:PSGSuiteKey -is [System.Byte[]]) { + $encParams["Key"] = $Global:PSGSuiteKey + } + elseif ($Global:PSGSuiteKey -is [System.Security.SecureString]) { + $encParams["SecureKey"] = $Global:PSGSuiteKey + } + 'ConvertTo-SecureString "{0}"' -f (ConvertFrom-SecureString $_ @encParams) + } + "Secure" = { + param([string]$String) + $encParams = @{} + if ($Global:PSGSuiteKey -is [System.Byte[]]) { + $encParams["Key"] = $Global:PSGSuiteKey + } + elseif ($Global:PSGSuiteKey -is [System.Security.SecureString]) { + $encParams["SecureKey"] = $Global:PSGSuiteKey + } + ConvertTo-SecureString $String @encParams + } + "ConvertTo-SecureString" = { + param([string]$String) + $encParams = @{} + if ($Global:PSGSuiteKey -is [System.Byte[]]) { + $encParams["Key"] = $Global:PSGSuiteKey + } + elseif ($Global:PSGSuiteKey -is [System.Security.SecureString]) { + $encParams["SecureKey"] = $Global:PSGSuiteKey + } + ConvertTo-SecureString $String @encParams + } +} + +try { + $confParams = @{ + Scope = $ConfigScope + } + if ($ConfigName) { + $confParams["ConfigName"] = $ConfigName + $Script:ConfigName = $ConfigName + } + try { + if ($global:PSGSuite) { + Write-Warning "Using config $(if ($global:PSGSuite.ConfigName){"name '$($global:PSGSuite.ConfigName)' "})found in variable: `$global:PSGSuite" + Write-Verbose "$(($global:PSGSuite | Format-List | Out-String).Trim())" + if ($global:PSGSuite -is [System.Collections.Hashtable]) { + $global:PSGSuite = New-Object PSObject -Property $global:PSGSuite + } + $script:PSGSuite = $global:PSGSuite + } + else { + Get-PSGSuiteConfig @confParams -ErrorAction Stop + } + } + catch { + if (Test-Path "$ModuleRoot\$env:USERNAME-$env:COMPUTERNAME-$env:PSGSuiteDefaultDomain-PSGSuite.xml") { + Get-PSGSuiteConfig -Path "$ModuleRoot\$env:USERNAME-$env:COMPUTERNAME-$env:PSGSuiteDefaultDomain-PSGSuite.xml" -ErrorAction Stop + Write-Warning "No Configuration.psd1 found at scope '$ConfigScope'; falling back to legacy XML. If you would like to convert your legacy XML to the newer Configuration.psd1, run the following command: + +Get-PSGSuiteConfig -Path '$ModuleRoot\$env:USERNAME-$env:COMPUTERNAME-$env:PSGSuiteDefaultDomain-PSGSuite.xml' -PassThru | Set-PSGSuiteConfig +" + } + else { + Write-Warning "There was no config returned! Please make sure you are using the correct key or have a configuration already saved." + } + } +} +catch { + Write-Warning "There was no config returned! Please make sure you are using the correct key or have a configuration already saved." +} \ No newline at end of file diff --git a/PSGSuite/Private/Import-GoogleSDK.ps1 b/PSGSuite/Private/Import-GoogleSDK.ps1 index 259f39d8..d95198f4 100644 --- a/PSGSuite/Private/Import-GoogleSDK.ps1 +++ b/PSGSuite/Private/Import-GoogleSDK.ps1 @@ -1,8 +1,9 @@ function Import-GoogleSDK { [CmdletBinding()] - Param() + Param( + [string]$lib = (Resolve-Path "$($script:ModuleRoot)\lib") + ) Process { - $lib = Resolve-Path "$($script:ModuleRoot)\lib" $refs = @() $sdkPath = $lib $dlls = Get-ChildItem $sdkPath -Filter "*.dll" diff --git a/build.ps1 b/build.ps1 index 47f3ff86..02e8b045 100644 --- a/build.ps1 +++ b/build.ps1 @@ -1,9 +1,9 @@ [cmdletbinding()] param( [parameter( Position = 0)] - [ValidateSet('Init','Clean','Compile','Import','Test','Full','Deploy','Skip','Docs')] + [ValidateSet('Init','Clean', 'Download', 'Generate', 'Compile','Import','Test','Full','Deploy','Skip','Docs')] [string[]] - $Task = @('Init','Clean','Compile','Import'), + $Task = @('Init','Clean', 'Download', 'Generate', 'Compile','Import'), [parameter()] [Alias('nr','nor')] [switch]$NoRestore, diff --git a/ci/templates/Module/Aliases.ps1 b/ci/templates/Module/Aliases.ps1 new file mode 100644 index 00000000..c7c313a3 --- /dev/null +++ b/ci/templates/Module/Aliases.ps1 @@ -0,0 +1,20 @@ +Param( + [parameter(mandatory=$true)]$SourceDirectory +) + +$aliasHashContents = (Get-Content (Join-Path $SourceDirectory "PSGSuite" "Aliases" "PSGSuite.Aliases.ps1") -Raw).Trim() + +@" +`$aliasHash = $aliasHashContents + +foreach (`$key in `$aliasHash.Keys) { + try { + New-Alias -Name `$key -Value `$aliasHash[`$key] -Force + } + catch { + Write-Error "[ALIAS: `$(`$key)] `$(`$_.Exception.Message.ToString())" + } +} + +Export-ModuleMember -Alias '*' +"@ \ No newline at end of file diff --git a/ci/templates/README.md b/ci/templates/README.md new file mode 100644 index 00000000..7cc74ce4 --- /dev/null +++ b/ci/templates/README.md @@ -0,0 +1,80 @@ +# Generation Templates + +This directory contains the templates used to programmatically generate module content during the build process. Each template is an independent `.ps1` file that outputs the generated content to the PowerShell pipeline as either a string or a hashtable of strings. + +## Template Directories + +By default the template directory tree mirrors the module source directory tree. When executed, all output will be written to the corresponding directory found in the module source directory. + +For example, the template `\Templates\Public\Authentication\Foo.ps1` will have it's output written to the directory `\PSGSuite\Public\Authentication\`. + +## Template Priority + +It may be beneficial to prioritise the order of execution for each template. It is possible to assign a priority value to each template between `1` execute first and `9` execute last. To assign a value, prefix the template name with `-`. + +If a priority value is not assigned to a template, it will be assigned priority `5` by default. + +For example, for the following templates: + +- `2-foo.ps1` is assigned priority `2` +- `bar.ps1` is assigned priority `5` + +## Template Output + +**String Output** + +When a template returns a string value it's output filename will be the template file name excluding any priority prefix. + +For example, the file `\Templates\Public\Authentication\2-Foo.ps1` will have it's output written to the file `\PSGSuite\Public\Authentication\Foo.ps1`. + +**Hashtable Output** + +When a template returns a hashtable, the value for each entry will be converted to a string and the key becomes the output file name including the file extension. + +For example, the file `\Templates\Public\Authentication\Foo.ps1` produces the following hashtable. + +``` +@{ + 'Get-Foo.ps1' = 'Write-Host "Got Foo!"' + 'Remove-Foo.ps1' = 'Write-Host "Removed Foo!"' +} +``` + +Two output files would be produced. + +- `\PSGSuite\Public\Authentication\Get-Foo.ps1` +- `\PSGSuite\Public\Authentication\Remove-Foo.ps1` + +So far the output files have been relative to the location of the template file. It is also possible to provide an absolute file path, relative to the source directory, as the entries key. When this is done, the output file path will be the specified absolute path. + +For example, the file `\Templates\Public\Authentication\Foo.ps1` produces the following hashtable. + +``` +@{ + '\Private\Authentication\Get-Foo.ps1' = 'Write-Host "Got Foo!"' + '\Show-Foo.ps1' = 'Write-Host "Show Foo!"' + 'Remove-Foo.ps1' = 'Write-Host "Removed Foo!"' +} +``` + +Three output files would be produced. + +- `\PSGSuite\Private\Authentication\Get-Foo.ps1` +- `\PSGSuite\Show-Foo.ps1` +- `\PSGSuite\Public\Authentication\Remove-Foo.ps1` + + +**Unsupported Output** + +When a template returns an unsupported type, it will be converted and processed as a string. + + +## Template Execution + +Template execution is triggered by the `generate` task of the PSGSuite build pipeline. + +The following parameters are passed to the template: + +- `-ModuleSource` - This is the file path to the repository root. + +All generated output files will each have a comment added at the top of the file indicating it was programmatically generated. diff --git a/psake.ps1 b/psake.ps1 index 81eb97e4..32715ed4 100644 --- a/psake.ps1 +++ b/psake.ps1 @@ -86,10 +86,127 @@ Task Clean -Depends Init { " Cleaned previous output directory [$outputDir]" } -Description 'Cleans module output directory' -Task Compile -Depends Clean { + +Task Download -Depends Clean { + if ("$env:NoNugetRestore" -ne 'True') { + New-Item -Path "$outputModVerDir\lib" -ItemType Directory -ErrorAction SilentlyContinue | Out-Null + Write-BuildLog "Installing NuGet dependencies..." + Install-NuGetDependencies -Destination $outputModVerDir -AddlSearchString $NuGetSearchStrings -Verbose + } + else { + Write-BuildLog "Skipping NuGet Restore due to `$env:NoNugetRestore = '$env:NoNugetRestore'" + } +} -Description 'Downloads module third-party dependencies' + +Task Generate -Depends Clean, Download { + # Load the Google SDKs for use during dynamic content generation + Write-BuildLog "Importing the Google SDK" + . (Join-Path $Sut 'Private\Import-GoogleSDK.ps1') + Import-GoogleSDK -Lib (Join-Path $outputModVerDir 'lib') + + # Dynamically generate module content by executing all .ps1 files in the template folder. + # Each template is expected to output a string value that will be saved in the corresponding file in the source folder. + # Child scope is used to minimise interference and pollution of build variables. + $TemplatesPath = Join-Path $PSScriptRoot "ci" "templates" + $SourceDirectory = $Sut + + $DefaultPriority = 5 + $TemplatePriorities = @{ + 1 = @() + 2 = @() + 3 = @() + 4 = @() + 5 = @() + 6 = @() + 7 = @() + 8 = @() + 9 = @() + } + Get-ChildItem -Path $TemplatesPath -Filter "*.ps1" -Recurse -File | ForEach-Object { + If ($_.Name -match "^(?[1-9])-"){ + $TemplatePriorities[[int]$Matches.priority] += $_ + } else { + $TemplatePriorities[$DefaultPriority] += $_ + } + } + + $ExecutionOrder = @() + ForEach ($Priority in @(1..9)){ + $TemplatePriorities[$Priority] = $TemplatePriorities[$Priority] | Sort-Object + $TemplatePriorities[$Priority] | ForEach-Object { + $ExecutionOrder += $_ + } + } + + $ExecutionOrder | ForEach-Object { + + $RelativeTemplatePath = $_.FullName.Substring($TemplatesPath.Length + 1) + $RelativeDirectory = $_.DirectoryName.Substring($TemplatesPath.Length + 1) + + Write-BuildLog "Executing template: $RelativeTemplatePath" + $TemplateResult = & $_.FullName -SourceDirectory $PSScriptRoot + + If ($TemplateResult){ + + $OutputDirectory = Join-Path $SourceDirectory $RelativeDirectory + + If ($TemplateResult -is [hashtable]){ + + ForEach ($key in $TemplateResult.keys){ + + If ($Key -match '^[\\/]'){ + $OutputPath = Join-Path $SourceDirectory $Key + } else { + $OutputPath = Join-Path $OutputDirectory $(Split-Path $Key -Leaf) + } + + if (-not (Test-Path (Split-Path $OutputPath -Parent))){ + New-Item -Path (Split-Path $OutputPath -Parent) -ItemType Directory -Force | Out-Null + } + + $OutputValue = $TemplateResult[$Key] + @" +# Programmatically generated from template '$RelativeTemplatePath' +# This file will be overwritten during the module build process. + +$OutputValue +"@ | Out-File -Path $OutputPath -Encoding UTF8 -Force + Write-BuildLog "Template output written to: $OutputPath" + + } + + } else { + If ($_.Name -Match "^[1-9]-"){ + $OutputPath = Join-Path $OutputDirectory $_.Name.Substring(2) + } else { + $OutputPath = Join-Path $OutputDirectory $_.Name + } + + if (-not (Test-Path (Split-Path $OutputPath -Parent))){ + New-Item -Path (Split-Path $OutputPath -Parent) -ItemType Directory -Force | Out-Null + } + + @" +# Programmatically generated from template '$RelativeTemplatePath' +# This file will be overwritten during the module build process. + +$TemplateResult +"@ | Out-File -Path $OutputPath -Encoding UTF8 -Force + Write-BuildLog "Template output written to: $OutputPath" + + } + + } else { + Write-BuildLog "Template did not output any content" -Severe + } + + Write-BuildLog "Completed template: $RelativeTemplatePath" + } +} -Description "Generates module content from template files" + +Task Compile -Depends Clean, Download, Generate { # Create module output directory $functionsToExport = @() - $sutLib = [System.IO.Path]::Combine($sut, 'lib') $aliasesToExport = (. $sut\Aliases\PSGSuite.Aliases.ps1).Keys if (-not (Test-Path $outputModVerDir)) { $modDir = New-Item -Path $outputModDir -ItemType Directory -ErrorAction SilentlyContinue @@ -100,7 +217,7 @@ Task Compile -Depends Clean { Write-BuildLog 'Creating psm1...' $psm1 = Copy-Item -Path (Join-Path -Path $sut -ChildPath 'PSGSuite.psm1') -Destination (Join-Path -Path $outputModVerDir -ChildPath "$($ENV:BHProjectName).psm1") -PassThru - foreach ($scope in @('Private', 'Public')) { + foreach ($scope in @('Class', 'Private', 'Public', 'Module')) { Write-BuildLog "Copying contents from files in source folder to PSM1: $($scope)" $gciPath = Join-Path $sut $scope if (Test-Path $gciPath) { @@ -115,146 +232,8 @@ Task Compile -Depends Clean { } } - Invoke-CommandWithLog { Remove-Module $env:BHProjectName -ErrorAction SilentlyContinue -Force -Verbose:$false } - if ("$env:NoNugetRestore" -ne 'True') { - New-Item -Path "$outputModVerDir\lib" -ItemType Directory -ErrorAction SilentlyContinue | Out-Null - Write-BuildLog "Installing NuGet dependencies..." - Install-NuGetDependencies -Destination $outputModVerDir -AddlSearchString $NuGetSearchStrings -Verbose - } - else { - Write-BuildLog "Skipping NuGet Restore due to `$env:NoNugetRestore = '$env:NoNugetRestore'" - } - - $aliasHashContents = (Get-Content "$sut\Aliases\PSGSuite.Aliases.ps1" -Raw).Trim() - - # Set remainder of PSM1 contents - @" - -Import-GoogleSDK - -if (`$global:PSGSuiteKey -and `$MyInvocation.BoundParameters['Debug']) { - `$prevDebugPref = `$DebugPreference - `$DebugPreference = "Continue" - Write-Debug "```$global:PSGSuiteKey is set to a `$(`$global:PSGSuiteKey.Count * 8)-bit key!" - `$DebugPreference = `$prevDebugPref -} - -`$aliasHash = $aliasHashContents - -foreach (`$key in `$aliasHash.Keys) { - try { - New-Alias -Name `$key -Value `$aliasHash[`$key] -Force - } - catch { - Write-Error "[ALIAS: `$(`$key)] `$(`$_.Exception.Message.ToString())" - } -} - -Export-ModuleMember -Alias '*' - -if (!(Test-Path (Join-Path "~" ".scrthq"))) { - New-Item -Path (Join-Path "~" ".scrthq") -ItemType Directory -Force | Out-Null -} - -if (`$PSVersionTable.ContainsKey('PSEdition') -and `$PSVersionTable.PSEdition -eq 'Core' -and !`$Global:PSGSuiteKey -and !`$IsWindows) { - if (!(Test-Path (Join-Path (Join-Path "~" ".scrthq") "BlockCoreCLREncryptionWarning.txt"))) { - Write-Warning "CoreCLR does not support DPAPI encryption! Setting a basic AES key to prevent errors. Please create a unique key as soon as possible as this will only obfuscate secrets from plain text in the Configuration, the key is not secure as is. If you would like to prevent this message from displaying in the future, run the following command:`n`nBlock-CoreCLREncryptionWarning`n" - } - `$Global:PSGSuiteKey = [Byte[]]@(1..16) - `$ConfigScope = "User" -} - -if (`$Global:PSGSuiteKey -is [System.Security.SecureString]) { - `$Method = "SecureString" - if (!`$ConfigScope) { - `$ConfigScope = "Machine" - } -} -elseif (`$Global:PSGSuiteKey -is [System.Byte[]]) { - `$Method = "AES Key" - if (!`$ConfigScope) { - `$ConfigScope = "Machine" - } -} -else { - `$Method = "DPAPI" - `$ConfigScope = "User" -} - -Add-MetadataConverter -Converters @{ - [SecureString] = { - `$encParams = @{} - if (`$Global:PSGSuiteKey -is [System.Byte[]]) { - `$encParams["Key"] = `$Global:PSGSuiteKey - } - elseif (`$Global:PSGSuiteKey -is [System.Security.SecureString]) { - `$encParams["SecureKey"] = `$Global:PSGSuiteKey - } - 'ConvertTo-SecureString "{0}"' -f (ConvertFrom-SecureString `$_ @encParams) - } - "Secure" = { - param([string]`$String) - `$encParams = @{} - if (`$Global:PSGSuiteKey -is [System.Byte[]]) { - `$encParams["Key"] = `$Global:PSGSuiteKey - } - elseif (`$Global:PSGSuiteKey -is [System.Security.SecureString]) { - `$encParams["SecureKey"] = `$Global:PSGSuiteKey - } - ConvertTo-SecureString `$String @encParams - } - "ConvertTo-SecureString" = { - param([string]`$String) - `$encParams = @{} - if (`$Global:PSGSuiteKey -is [System.Byte[]]) { - `$encParams["Key"] = `$Global:PSGSuiteKey - } - elseif (`$Global:PSGSuiteKey -is [System.Security.SecureString]) { - `$encParams["SecureKey"] = `$Global:PSGSuiteKey - } - ConvertTo-SecureString `$String @encParams - } -} - -try { - `$confParams = @{ - Scope = `$ConfigScope - } - if (`$ConfigName) { - `$confParams["ConfigName"] = `$ConfigName - `$Script:ConfigName = `$ConfigName - } - try { - if (`$global:PSGSuite) { - Write-Warning "Using config `$(if (`$global:PSGSuite.ConfigName){"name '`$(`$global:PSGSuite.ConfigName)' "})found in variable: ```$global:PSGSuite" - Write-Verbose "`$((`$global:PSGSuite | Format-List | Out-String).Trim())" - if (`$global:PSGSuite -is [System.Collections.Hashtable]) { - `$global:PSGSuite = New-Object PSObject -Property `$global:PSGSuite - } - `$script:PSGSuite = `$global:PSGSuite - } - else { - Get-PSGSuiteConfig @confParams -ErrorAction Stop - } - } - catch { - if (Test-Path "`$ModuleRoot\`$env:USERNAME-`$env:COMPUTERNAME-`$env:PSGSuiteDefaultDomain-PSGSuite.xml") { - Get-PSGSuiteConfig -Path "`$ModuleRoot\`$env:USERNAME-`$env:COMPUTERNAME-`$env:PSGSuiteDefaultDomain-PSGSuite.xml" -ErrorAction Stop - Write-Warning "No Configuration.psd1 found at scope '`$ConfigScope'; falling back to legacy XML. If you would like to convert your legacy XML to the newer Configuration.psd1, run the following command:`n`nGet-PSGSuiteConfig -Path '`$ModuleRoot\`$env:USERNAME-`$env:COMPUTERNAME-`$env:PSGSuiteDefaultDomain-PSGSuite.xml' -PassThru | Set-PSGSuiteConfig`n" - } - else { - Write-Warning "There was no config returned! Please make sure you are using the correct key or have a configuration already saved." - } - } -} -catch { - Write-Warning "There was no config returned! Please make sure you are using the correct key or have a configuration already saved." -} - -"@ | Add-Content -Path $psm1 -Encoding UTF8 - # Copy over manifest Copy-Item -Path $env:BHPSModuleManifest -Destination $outputModVerDir From b9240196f9c05db53d1ffb18b9412eb33bf97b3b Mon Sep 17 00:00:00 2001 From: Foggy2 <23325075+Foggy2@users.noreply.github.com> Date: Mon, 16 Jun 2025 19:45:57 +0930 Subject: [PATCH 04/13] Fixed path handling for templates in the template root directory. --- psake.ps1 | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/psake.ps1 b/psake.ps1 index 32715ed4..d72bad12 100644 --- a/psake.ps1 +++ b/psake.ps1 @@ -141,7 +141,10 @@ Task Generate -Depends Clean, Download { $ExecutionOrder | ForEach-Object { $RelativeTemplatePath = $_.FullName.Substring($TemplatesPath.Length + 1) - $RelativeDirectory = $_.DirectoryName.Substring($TemplatesPath.Length + 1) + $RelativeDirectory = $_.DirectoryName.Substring($TemplatesPath.Length) + If ($RelativeDirectory -match '^[\\/]'){ + $RelativeDirectory = $RelativeDirectory.Substring(1) + } Write-BuildLog "Executing template: $RelativeTemplatePath" $TemplateResult = & $_.FullName -SourceDirectory $PSScriptRoot From 2b5791a1772af29049f25af0d4ce67f455afcb4c Mon Sep 17 00:00:00 2001 From: Foggy2 <23325075+Foggy2@users.noreply.github.com> Date: Sat, 21 Jun 2025 16:02:04 +0930 Subject: [PATCH 05/13] Added `-DebugBuild` parameter to build.ps1 --- CHANGELOG.md | 3 +++ CONTRIBUTING.md | 8 ++++++-- build.ps1 | 4 +++- psake.ps1 | 52 ++++++++++++++++++++++++++++++++++++++----------- 4 files changed, 53 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 60e8fb38..49dc871c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -126,6 +126,9 @@ - Moved the existing module initialization code out of the `Compile` task of the module build process and split into two parts: - The dynamic alias logic has been moved into the `templates\Module\Aliases.ps1` template file. - The static module initialization logic has been moved into the `Module\Initialization.ps1` file. +- Added `-DebugBuild` switch to `build.ps1` for improved module debugging. When built with this switch the compiled `PSGSuite.psm1` file will: + - Link directly to each source code file found in the `PSGSuite` directory. + - Export all module functions and variables to the PowerShell session. ## 3.0.0 - 2024-11-20 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6bb63e40..25b6035a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -115,8 +115,12 @@ Install MkDocs and its dependencies using the provided requirements.txt file. ### Enabling Debug Mode -> [!WARNING] -> TODO: Add instructions for how to use `build.ps1` to enable debug mode for the module. +A debug build of the module can be built by providing the `-DebugBuild` switch to `build.ps1`. + +Debug builds contain the following changes: + +- All source code is dot sourced within the compiled module instead of being copied directly into the `psgsuite.psm1` file. +- All module functions and variables are exported to the PowerShell session. ### Google .NET SDK Documentation diff --git a/build.ps1 b/build.ps1 index 02e8b045..4a29de98 100644 --- a/build.ps1 +++ b/build.ps1 @@ -10,7 +10,8 @@ param( [switch]$UpdateModules, [switch]$Force, - [switch]$Help + [switch]$Help, + [switch]$DebugBuild ) $env:_BuildStart = Get-Date -Format 'o' $ModuleName = 'PSGSuite' @@ -45,6 +46,7 @@ else { $env:BuildProjectName = $ModuleName $env:BuildScriptPath = $PSScriptRoot + $env:Build_Debug = $DebugBuild if ($Task -contains 'Docs') { $env:NoNugetRestore = $true diff --git a/psake.ps1 b/psake.ps1 index d72bad12..6338247b 100644 --- a/psake.ps1 +++ b/psake.ps1 @@ -220,19 +220,41 @@ Task Compile -Depends Clean, Download, Generate { Write-BuildLog 'Creating psm1...' $psm1 = Copy-Item -Path (Join-Path -Path $sut -ChildPath 'PSGSuite.psm1') -Destination (Join-Path -Path $outputModVerDir -ChildPath "$($ENV:BHProjectName).psm1") -PassThru - foreach ($scope in @('Class', 'Private', 'Public', 'Module')) { - Write-BuildLog "Copying contents from files in source folder to PSM1: $($scope)" - $gciPath = Join-Path $sut $scope - if (Test-Path $gciPath) { - Get-ChildItem -Path $gciPath -Filter "*.ps1" -Recurse -File | ForEach-Object { - Write-BuildLog "Working on: $scope$([System.IO.Path]::DirectorySeparatorChar)$($_.FullName.Replace("$gciPath$([System.IO.Path]::DirectorySeparatorChar)",'') -replace '\.ps1$')" - [System.IO.File]::AppendAllText($psm1, ("$([System.IO.File]::ReadAllText($_.FullName))`n")) - if ($scope -eq 'Public') { - $functionsToExport += $_.BaseName - [System.IO.File]::AppendAllText($psm1, ("Export-ModuleMember -Function '$($_.BaseName)'`n")) + If (-not ($ENV:Build_Debug -eq "True")){ + + # Normal build + # Source code will be copied into the compiled module + foreach ($scope in @('Class', 'Private', 'Public', 'Module')) { + Write-BuildLog "Copying contents from files in source folder to PSM1: $($scope)" + $gciPath = Join-Path $sut $scope + if (Test-Path $gciPath) { + Get-ChildItem -Path $gciPath -Filter "*.ps1" -Recurse -File | ForEach-Object { + Write-BuildLog "Working on: $scope$([System.IO.Path]::DirectorySeparatorChar)$($_.FullName.Replace("$gciPath$([System.IO.Path]::DirectorySeparatorChar)",'') -replace '\.ps1$')" + [System.IO.File]::AppendAllText($psm1, ("$([System.IO.File]::ReadAllText($_.FullName))`n")) + if ($scope -eq 'Public') { + $functionsToExport += $_.BaseName + [System.IO.File]::AppendAllText($psm1, ("Export-ModuleMember -Function '$($_.BaseName)'`n")) + } } } } + + } else { + + # Debug Build + # The module will link directly to the .ps1 files in the source directory for convenient debugging. + foreach ($scope in @('Class', 'Private', 'Public', 'Module')) { + Write-BuildLog "Linking files in source folder to PSM1: $($scope)" + $gciPath = Join-Path $sut $scope + if (Test-Path $gciPath) { + Get-ChildItem -Path $gciPath -Filter "*.ps1" -Recurse -File | ForEach-Object { + Write-BuildLog "Working on: $scope$([System.IO.Path]::DirectorySeparatorChar)$($_.FullName.Replace("$gciPath$([System.IO.Path]::DirectorySeparatorChar)",'') -replace '\.ps1$')" + [System.IO.File]::AppendAllText($psm1, (". '$($_.FullName)'`n")) + } + } + } + [System.IO.File]::AppendAllText($psm1, ("Export-ModuleMember -Function * -Variable * -Alias *")) + } Invoke-CommandWithLog { Remove-Module $env:BHProjectName -ErrorAction SilentlyContinue -Force -Verbose:$false } @@ -241,7 +263,15 @@ Task Compile -Depends Clean, Download, Generate { Copy-Item -Path $env:BHPSModuleManifest -Destination $outputModVerDir # Update FunctionsToExport on manifest - Update-ModuleManifest -Path (Join-Path $outputModVerDir "$($env:BHProjectName).psd1") -FunctionsToExport ($functionsToExport | Sort-Object) -AliasesToExport ($aliasesToExport | Sort-Object) + If (-not ($ENV:Build_Debug -eq "True")){ + # Normal build + Update-ModuleManifest -Path (Join-Path $outputModVerDir "$($env:BHProjectName).psd1") -FunctionsToExport ($functionsToExport | Sort-Object) -AliasesToExport ($aliasesToExport | Sort-Object) + } else { + #Debug build + write-host $outputModVerDir + write-host "$($env:BHProjectName).psd1" + Update-ModuleManifest -Path (Join-Path $outputModVerDir "$($env:BHProjectName).psd1") -FunctionsToExport '*' -AliasesToExport '*' -VariablesToExport '*' + } if ((Get-ChildItem $outputModVerDir | Where-Object { $_.Name -eq "$($env:BHProjectName).psd1" }).BaseName -cne $env:BHProjectName) { " Renaming manifest to correct casing" From 4f3f7453d0188bd71b808b7e86fefe297fe98db6 Mon Sep 17 00:00:00 2001 From: Foggy2 <23325075+Foggy2@users.noreply.github.com> Date: Sat, 21 Jun 2025 18:42:47 +0930 Subject: [PATCH 06/13] Changed the template names added to all generated files to always be lowercase. --- PSGSuite/Module/Aliases.ps1 | 2 +- psake.ps1 | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/PSGSuite/Module/Aliases.ps1 b/PSGSuite/Module/Aliases.ps1 index 482ad895..1329ba39 100644 --- a/PSGSuite/Module/Aliases.ps1 +++ b/PSGSuite/Module/Aliases.ps1 @@ -1,4 +1,4 @@ -# Programmatically generated from template 'Module\Aliases.ps1' +# Programmatically generated from template 'module\aliases.ps1' # This file will be overwritten during the module build process. $aliasHash = # Alias => => => => => => => => Function diff --git a/psake.ps1 b/psake.ps1 index d72bad12..255da2b3 100644 --- a/psake.ps1 +++ b/psake.ps1 @@ -169,7 +169,7 @@ Task Generate -Depends Clean, Download { $OutputValue = $TemplateResult[$Key] @" -# Programmatically generated from template '$RelativeTemplatePath' +# Programmatically generated from template '$($RelativeTemplatePath.ToLower())' # This file will be overwritten during the module build process. $OutputValue @@ -190,7 +190,7 @@ $OutputValue } @" -# Programmatically generated from template '$RelativeTemplatePath' +# Programmatically generated from template '$($RelativeTemplatePath.ToLower())' # This file will be overwritten during the module build process. $TemplateResult From 5ab52e90538f8d1678a59d13d3ba649433e9f3ec Mon Sep 17 00:00:00 2001 From: Foggy2 <23325075+Foggy2@users.noreply.github.com> Date: Sat, 21 Jun 2025 18:44:08 +0930 Subject: [PATCH 07/13] Added `-DebugBuild` parameter to build.ps1 --- CHANGELOG.md | 3 +++ CONTRIBUTING.md | 8 ++++++-- build.ps1 | 4 +++- psake.ps1 | 52 ++++++++++++++++++++++++++++++++++++++----------- 4 files changed, 53 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 60e8fb38..49dc871c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -126,6 +126,9 @@ - Moved the existing module initialization code out of the `Compile` task of the module build process and split into two parts: - The dynamic alias logic has been moved into the `templates\Module\Aliases.ps1` template file. - The static module initialization logic has been moved into the `Module\Initialization.ps1` file. +- Added `-DebugBuild` switch to `build.ps1` for improved module debugging. When built with this switch the compiled `PSGSuite.psm1` file will: + - Link directly to each source code file found in the `PSGSuite` directory. + - Export all module functions and variables to the PowerShell session. ## 3.0.0 - 2024-11-20 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6bb63e40..25b6035a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -115,8 +115,12 @@ Install MkDocs and its dependencies using the provided requirements.txt file. ### Enabling Debug Mode -> [!WARNING] -> TODO: Add instructions for how to use `build.ps1` to enable debug mode for the module. +A debug build of the module can be built by providing the `-DebugBuild` switch to `build.ps1`. + +Debug builds contain the following changes: + +- All source code is dot sourced within the compiled module instead of being copied directly into the `psgsuite.psm1` file. +- All module functions and variables are exported to the PowerShell session. ### Google .NET SDK Documentation diff --git a/build.ps1 b/build.ps1 index 02e8b045..4a29de98 100644 --- a/build.ps1 +++ b/build.ps1 @@ -10,7 +10,8 @@ param( [switch]$UpdateModules, [switch]$Force, - [switch]$Help + [switch]$Help, + [switch]$DebugBuild ) $env:_BuildStart = Get-Date -Format 'o' $ModuleName = 'PSGSuite' @@ -45,6 +46,7 @@ else { $env:BuildProjectName = $ModuleName $env:BuildScriptPath = $PSScriptRoot + $env:Build_Debug = $DebugBuild if ($Task -contains 'Docs') { $env:NoNugetRestore = $true diff --git a/psake.ps1 b/psake.ps1 index 255da2b3..64875c36 100644 --- a/psake.ps1 +++ b/psake.ps1 @@ -220,19 +220,41 @@ Task Compile -Depends Clean, Download, Generate { Write-BuildLog 'Creating psm1...' $psm1 = Copy-Item -Path (Join-Path -Path $sut -ChildPath 'PSGSuite.psm1') -Destination (Join-Path -Path $outputModVerDir -ChildPath "$($ENV:BHProjectName).psm1") -PassThru - foreach ($scope in @('Class', 'Private', 'Public', 'Module')) { - Write-BuildLog "Copying contents from files in source folder to PSM1: $($scope)" - $gciPath = Join-Path $sut $scope - if (Test-Path $gciPath) { - Get-ChildItem -Path $gciPath -Filter "*.ps1" -Recurse -File | ForEach-Object { - Write-BuildLog "Working on: $scope$([System.IO.Path]::DirectorySeparatorChar)$($_.FullName.Replace("$gciPath$([System.IO.Path]::DirectorySeparatorChar)",'') -replace '\.ps1$')" - [System.IO.File]::AppendAllText($psm1, ("$([System.IO.File]::ReadAllText($_.FullName))`n")) - if ($scope -eq 'Public') { - $functionsToExport += $_.BaseName - [System.IO.File]::AppendAllText($psm1, ("Export-ModuleMember -Function '$($_.BaseName)'`n")) + If (-not ($ENV:Build_Debug -eq "True")){ + + # Normal build + # Source code will be copied into the compiled module + foreach ($scope in @('Class', 'Private', 'Public', 'Module')) { + Write-BuildLog "Copying contents from files in source folder to PSM1: $($scope)" + $gciPath = Join-Path $sut $scope + if (Test-Path $gciPath) { + Get-ChildItem -Path $gciPath -Filter "*.ps1" -Recurse -File | ForEach-Object { + Write-BuildLog "Working on: $scope$([System.IO.Path]::DirectorySeparatorChar)$($_.FullName.Replace("$gciPath$([System.IO.Path]::DirectorySeparatorChar)",'') -replace '\.ps1$')" + [System.IO.File]::AppendAllText($psm1, ("$([System.IO.File]::ReadAllText($_.FullName))`n")) + if ($scope -eq 'Public') { + $functionsToExport += $_.BaseName + [System.IO.File]::AppendAllText($psm1, ("Export-ModuleMember -Function '$($_.BaseName)'`n")) + } } } } + + } else { + + # Debug Build + # The module will link directly to the .ps1 files in the source directory for convenient debugging. + foreach ($scope in @('Class', 'Private', 'Public', 'Module')) { + Write-BuildLog "Linking files in source folder to PSM1: $($scope)" + $gciPath = Join-Path $sut $scope + if (Test-Path $gciPath) { + Get-ChildItem -Path $gciPath -Filter "*.ps1" -Recurse -File | ForEach-Object { + Write-BuildLog "Working on: $scope$([System.IO.Path]::DirectorySeparatorChar)$($_.FullName.Replace("$gciPath$([System.IO.Path]::DirectorySeparatorChar)",'') -replace '\.ps1$')" + [System.IO.File]::AppendAllText($psm1, (". '$($_.FullName)'`n")) + } + } + } + [System.IO.File]::AppendAllText($psm1, ("Export-ModuleMember -Function * -Variable * -Alias *")) + } Invoke-CommandWithLog { Remove-Module $env:BHProjectName -ErrorAction SilentlyContinue -Force -Verbose:$false } @@ -241,7 +263,15 @@ Task Compile -Depends Clean, Download, Generate { Copy-Item -Path $env:BHPSModuleManifest -Destination $outputModVerDir # Update FunctionsToExport on manifest - Update-ModuleManifest -Path (Join-Path $outputModVerDir "$($env:BHProjectName).psd1") -FunctionsToExport ($functionsToExport | Sort-Object) -AliasesToExport ($aliasesToExport | Sort-Object) + If (-not ($ENV:Build_Debug -eq "True")){ + # Normal build + Update-ModuleManifest -Path (Join-Path $outputModVerDir "$($env:BHProjectName).psd1") -FunctionsToExport ($functionsToExport | Sort-Object) -AliasesToExport ($aliasesToExport | Sort-Object) + } else { + #Debug build + write-host $outputModVerDir + write-host "$($env:BHProjectName).psd1" + Update-ModuleManifest -Path (Join-Path $outputModVerDir "$($env:BHProjectName).psd1") -FunctionsToExport '*' -AliasesToExport '*' -VariablesToExport '*' + } if ((Get-ChildItem $outputModVerDir | Where-Object { $_.Name -eq "$($env:BHProjectName).psd1" }).BaseName -cne $env:BHProjectName) { " Renaming manifest to correct casing" From 400f251747e854b4c709be91915f4d54de94b9f7 Mon Sep 17 00:00:00 2001 From: Foggy2 <23325075+Foggy2@users.noreply.github.com> Date: Mon, 23 Jun 2025 16:32:50 +0930 Subject: [PATCH 08/13] Adjusted build.ps1 to always copy source code for classes into the psm1 file --- psake.ps1 | 54 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/psake.ps1 b/psake.ps1 index 64875c36..c9cbfe07 100644 --- a/psake.ps1 +++ b/psake.ps1 @@ -216,45 +216,47 @@ Task Compile -Depends Clean, Download, Generate { New-Item -Path $outputModVerDir -ItemType Directory -ErrorAction SilentlyContinue | Out-Null } - # Append items to psm1 + # Create the PSM1 using module source files Write-BuildLog 'Creating psm1...' $psm1 = Copy-Item -Path (Join-Path -Path $sut -ChildPath 'PSGSuite.psm1') -Destination (Join-Path -Path $outputModVerDir -ChildPath "$($ENV:BHProjectName).psm1") -PassThru - - If (-not ($ENV:Build_Debug -eq "True")){ - - # Normal build - # Source code will be copied into the compiled module - foreach ($scope in @('Class', 'Private', 'Public', 'Module')) { + + # Normal builds: + # All source files will be copied directly into the PSM1 file. + # Debug Builds: + # All Public, Private and Module source code will be dot sourced inside the PSM1 file. + # Classes cannot be dot sourced within a module, so they will always be copied. + # See: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_classes?view=powershell-7.5#manually-importing-classes-from-a-powershell-module + foreach ($scope in @('Class', 'Private', 'Public', 'Module')) { + If (($Scope -eq "Class") -or (-not ($ENV:Build_Debug -eq "True"))){ + #Normal or Class Write-BuildLog "Copying contents from files in source folder to PSM1: $($scope)" - $gciPath = Join-Path $sut $scope - if (Test-Path $gciPath) { - Get-ChildItem -Path $gciPath -Filter "*.ps1" -Recurse -File | ForEach-Object { - Write-BuildLog "Working on: $scope$([System.IO.Path]::DirectorySeparatorChar)$($_.FullName.Replace("$gciPath$([System.IO.Path]::DirectorySeparatorChar)",'') -replace '\.ps1$')" + } else { + #Debug + Write-BuildLog "Linking files in source folder to PSM1: $($scope)" + } + + $gciPath = Join-Path $sut $scope + if (Test-Path $gciPath) { + Get-ChildItem -Path $gciPath -Filter "*.ps1" -Recurse -File | ForEach-Object { + Write-BuildLog "Working on: $scope$([System.IO.Path]::DirectorySeparatorChar)$($_.FullName.Replace("$gciPath$([System.IO.Path]::DirectorySeparatorChar)",'') -replace '\.ps1$')" + If (($Scope -eq "Class") -or (-not ($ENV:Build_Debug -eq "True"))){ + #Normal or Class [System.IO.File]::AppendAllText($psm1, ("$([System.IO.File]::ReadAllText($_.FullName))`n")) if ($scope -eq 'Public') { $functionsToExport += $_.BaseName [System.IO.File]::AppendAllText($psm1, ("Export-ModuleMember -Function '$($_.BaseName)'`n")) } - } - } - } - - } else { - - # Debug Build - # The module will link directly to the .ps1 files in the source directory for convenient debugging. - foreach ($scope in @('Class', 'Private', 'Public', 'Module')) { - Write-BuildLog "Linking files in source folder to PSM1: $($scope)" - $gciPath = Join-Path $sut $scope - if (Test-Path $gciPath) { - Get-ChildItem -Path $gciPath -Filter "*.ps1" -Recurse -File | ForEach-Object { - Write-BuildLog "Working on: $scope$([System.IO.Path]::DirectorySeparatorChar)$($_.FullName.Replace("$gciPath$([System.IO.Path]::DirectorySeparatorChar)",'') -replace '\.ps1$')" + } else { + #Debug [System.IO.File]::AppendAllText($psm1, (". '$($_.FullName)'`n")) } + + } } + } + If ($ENV:Build_Debug -eq "True"){ [System.IO.File]::AppendAllText($psm1, ("Export-ModuleMember -Function * -Variable * -Alias *")) - } Invoke-CommandWithLog { Remove-Module $env:BHProjectName -ErrorAction SilentlyContinue -Force -Verbose:$false } From 9c897884afcaac383e6f7f3fad396eadce209bf9 Mon Sep 17 00:00:00 2001 From: Foggy2 <23325075+Foggy2@users.noreply.github.com> Date: Mon, 23 Jun 2025 16:34:25 +0930 Subject: [PATCH 09/13] Added `Get-PSGSuiteOAuthScope` and `ci\templates\OAuthScopes.ps1` --- CHANGELOG.md | 12 + PSGSuite/Aliases/PSGSuite.Aliases.ps1 | 1 + .../Class/PSGSuiteValidFunctionValues.ps1 | 230 +++ .../Class/PSGSuiteValidOAuthScopeValues.ps1 | 39 + PSGSuite/Class/PSGSuiteValidServiceValues.ps1 | 23 + PSGSuite/Module/Aliases.ps1 | 1 + PSGSuite/Module/OAuthScopes.ps1 | 1323 +++++++++++++++++ .../Authentication/Get-PSGSuiteOAuthScope.ps1 | 152 ++ ci/templates/OAuthScopes.ps1 | 251 ++++ 9 files changed, 2032 insertions(+) create mode 100644 PSGSuite/Class/PSGSuiteValidFunctionValues.ps1 create mode 100644 PSGSuite/Class/PSGSuiteValidOAuthScopeValues.ps1 create mode 100644 PSGSuite/Class/PSGSuiteValidServiceValues.ps1 create mode 100644 PSGSuite/Module/OAuthScopes.ps1 create mode 100644 PSGSuite/Public/Authentication/Get-PSGSuiteOAuthScope.ps1 create mode 100644 ci/templates/OAuthScopes.ps1 diff --git a/CHANGELOG.md b/CHANGELOG.md index 49dc871c..75d767cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -129,6 +129,18 @@ - Added `-DebugBuild` switch to `build.ps1` for improved module debugging. When built with this switch the compiled `PSGSuite.psm1` file will: - Link directly to each source code file found in the `PSGSuite` directory. - Export all module functions and variables to the PowerShell session. +- Added `Get-PSGSuiteOAuthScope` function that returns the OAuth scopes that are currently used by PSGSuite. +- Added `Get-PSGSuiteScope` alias to the `Get-PSGSuiteOAuthScope` function. +- Added `PSGSuiteValidServiceValues` class that contains and validates the Google API service names that are used by PSGSuite. eg, `Google.Apis.Slides.v1.SlidesService` +- Added `PSGSuiteValidFunctionValues` class that contains and validates the list of public PSGSuite function names. eg, `Get-GSPresentation` +- Added `PSGSuiteValidOAuthScopeValues` class that contains and validates the list of OAuth scopes that are used by PSGSuite. eg, `https://www.googleapis.com/auth/drive` +- Added `ci\templates\OAuthScopes.ps1` generation template that scans the PSGSuite source directory for the OAuth scopes, function names and Google API service names that are used by PSGSuite. The discovered data is then used to programmatically produce the following items: + - `Module\OAuthScopes.ps1` - Contains the module variable `$script:_PSGSuiteOAuthScopes` that contains the dataset used by `Get-PSGSuiteOAuthScope` + - `Class\PSGSuiteValidServiceValues.ps1` + - `Class\PSGSuiteValidFunctionValues.ps1` + - `Class\PSGSuiteValidOAuthScopeValues.ps1` + + ## 3.0.0 - 2024-11-20 diff --git a/PSGSuite/Aliases/PSGSuite.Aliases.ps1 b/PSGSuite/Aliases/PSGSuite.Aliases.ps1 index ec8471ac..d36c904a 100644 --- a/PSGSuite/Aliases/PSGSuite.Aliases.ps1 +++ b/PSGSuite/Aliases/PSGSuite.Aliases.ps1 @@ -42,4 +42,5 @@ 'Update-GSGmailSendAsSettings' = 'Update-GSGmailSendAsAlias' 'Update-GSSheetValue' = 'Export-GSSheet' 'Update-GSTeamDrive' = 'Update-GSDrive' + 'Get-PSGSuiteScope' = 'Get-PSGSuiteOAuthScope' } diff --git a/PSGSuite/Class/PSGSuiteValidFunctionValues.ps1 b/PSGSuite/Class/PSGSuiteValidFunctionValues.ps1 new file mode 100644 index 00000000..214c02b3 --- /dev/null +++ b/PSGSuite/Class/PSGSuiteValidFunctionValues.ps1 @@ -0,0 +1,230 @@ +# Programmatically generated from template 'OAuthScopes.ps1' +# This file will be overwritten during the module build process. + +# Class that provides parameter validation for the names of the public PSGSuite functions. +class PSGSuiteValidFunctionValues : System.Management.Automation.IValidateSetValuesGenerator { + [string[]] GetValidValues() { + $Values = @( + 'Remove-GSStudentGuardian', + 'Remove-GSUserLicense', + 'New-GSGroup', + 'New-GSDomainAlias', + 'Update-GSGmailImapSettings', + 'Get-GSCalendarACL', + 'Unblock-CoreCLREncryptionWarning', + 'Start-GSDriveFileUpload', + 'Show-PSGSuiteConfig', + 'Remove-GSDrivePermission', + 'Remove-GSGroup', + 'Update-GSResource', + 'Set-GSDocContent', + 'Update-GSDriveRevision', + 'Remove-GSGroupAlias', + 'Clear-GSSheet', + 'Remove-GSAdminRole', + 'New-GSDriveFile', + 'Get-GSGroupAlias', + 'Get-GSUserLicense', + 'Update-GSSheet', + 'Get-GSUserToken', + 'Get-GSGmailVacationSettings', + 'Remove-GSDriveRevision', + 'Update-GSUserPhoto', + 'Remove-GSUserPhoto', + 'Remove-GSGmailDelegate', + 'Get-GSDriveFile', + 'Update-GSCalendarEvent', + 'New-GSCourse', + 'Remove-GSCalendarEvent', + 'Remove-GSDriveFile', + 'Add-GSCalendarEventReminder', + 'Remove-GSUserAlias', + 'Add-GSPrincipalGroupMembership', + 'Remove-GSCalendarAcl', + 'Update-GSGmailPopSettings', + 'Get-GSCourseInvitation', + 'Remove-GSUserASP', + 'Remove-GSAdminRoleAssignment', + 'Add-GSUserAddress', + 'Get-GSChatSpace', + 'Block-CoreCLREncryptionWarning', + 'New-GSGroupAlias', + 'Send-GSChatMessage', + 'Update-GSMobileDevice', + 'Update-GSGroupMember', + 'Export-GSDriveFile', + 'Get-GSDriveRevision', + 'Get-GSDriveFileUploadStatus', + 'Watch-GSDriveUpload', + 'Get-GSChatMessage', + 'Get-GSOrganizationalUnit', + 'New-GSPresentationUpdateRequest', + 'Import-GSSheet', + 'New-GSGmailSendAsAlias', + 'Update-GSDrive', + 'Remove-GSOrganizationalUnit', + 'Copy-GSDriveFile', + 'Remove-GSGmailSendAsAlias', + 'Restore-GSUser', + 'New-GSGmailSMIMEInfo', + 'Update-GSGroup', + 'Get-GSGmailAutoForwardingSettings', + 'Add-GSChatTextParagraph', + 'Get-GSUserAlias', + 'Remove-GSChatMessage', + 'Add-GSChatOnClick', + 'Update-GSGmailVacationSettings', + 'Remove-GSGmailFilter', + 'Add-GSChatKeyValue', + 'Get-GSUsageReport', + 'Get-GSUserSchema', + 'Get-GSGmailPopSettings', + 'New-GSDrive', + 'Add-GSCustomerPostalAddress', + 'Update-GSUserSchema', + 'Get-GSDomain', + 'Get-GSGroupSettings', + 'Get-GSGmailFilter', + 'Get-GSStudentGuardian', + 'Update-GSGmailSendAsAlias', + 'Add-GSDocContent', + 'Remove-GSUserToken', + 'Get-GSCourse', + 'Clear-PSGSuiteServiceCache', + 'Get-GSGmailSendAsAlias', + 'Set-PSGSuiteConfig', + 'Add-GSCalendarSubscription', + 'Import-PSGSuiteConfig', + 'New-GSUserSchema', + 'Get-GSDriveFolderSize', + 'Invoke-GSUserOffboarding', + 'Remove-GSDrive', + 'Add-GSChatCard', + 'Update-GSCourse', + 'Remove-GSCourseParticipant', + 'Get-GSDrivePermission', + 'Get-GSActivityReport', + 'Set-GSUserSchema', + 'Get-GSGmailLanguageSettings', + 'Revoke-GSStudentGuardianInvitation', + 'Get-GSUserASP', + 'Get-GSDrive', + 'Add-GSUserPhone', + 'Set-GSUserLicense', + 'Get-GSResource', + 'New-GSUserVerificationCodes', + 'Set-GSGroupSettings', + 'New-GSDomain', + 'Get-PSGSuiteConfig', + 'New-GSCalendarACL', + 'Add-GSCourseParticipant', + 'Get-GSMobileDevice', + 'Remove-GSGroupMember', + 'Get-GSStudentGuardianInvitation', + 'Get-GSSheetInfo', + 'Get-GSDocContent', + 'Start-GSDataTransfer', + 'Update-GSUser', + 'Remove-GSUserSchema', + 'Add-GSGmailForwardingAddress', + 'Get-GSDriveFileList', + 'Add-GSGmailSmtpMsa', + 'Get-GSGmailSMIMEInfo', + 'Add-GSGmailFilter', + 'Update-GSGmailSignature', + 'Update-GSOrganizationalUnit', + 'Compare-ModuleVersion', + 'New-GSAdminRole', + 'Confirm-GSCourseInvitation', + 'Get-GSCalendarEvent', + 'Remove-GSCourse', + 'Update-GSCalendarSubscription', + 'Sync-GSUserCache', + 'Add-GSUserOrganization', + 'Update-GSUserLicense', + 'Update-GSChatMessage', + 'Get-GSDataTransfer', + 'New-GSCourseInvitation', + 'Revoke-GSUserVerificationCodes', + 'Stop-GSDriveFileUpload', + 'Remove-GSGmailSMIMEInfo', + 'Edit-GSPresentation', + 'Add-GSChatButton', + 'Get-GSClassroomUserProfile', + 'Get-GSGmailDelegate', + 'Remove-GSCourseAlias', + 'Remove-GSCourseInvitation', + 'Get-GSCalendarSubscription', + 'Get-GSCourseParticipant', + 'New-GSUserAlias', + 'Show-GSDrive', + 'Copy-GSSheet', + 'Get-GSPresentation', + 'Update-GSChromeOSDevice', + 'Remove-GSCalendarSubscription', + 'Remove-GSUser', + 'New-GSOrganizationalUnit', + 'Add-GSUserLocation', + 'Add-GSGmailDelegate', + 'Remove-GSDomainAlias', + 'Update-GSDriveFile', + 'Add-GSChatCardSection', + 'Add-GSCalendarNotification', + 'Update-GSGmailLanguageSettings', + 'Add-GSUserSchemaField', + 'Get-GSGmailForwardingAddress', + 'Add-GSSheetValues', + 'Get-PSGSuiteServiceCache', + 'New-GSStudentGuardianInvitation', + 'New-GSCourseAlias', + 'Add-GSChatCardAction', + 'Remove-GSDomain', + 'Get-GSDriveProfile', + 'Hide-GSDrive', + 'New-GSSheet', + 'New-GSCalendarEvent', + 'Get-PSGSuiteOAuthScope', + 'Get-GSUserPhoto', + 'Switch-PSGSuiteConfig', + 'Update-GSGmailAutoForwardingSettings', + 'Export-GSSheet', + 'Get-GSUser', + 'Add-GSDrivePermission', + 'New-GSResource', + 'Get-GSDomainAlias', + 'Export-PSGSuiteConfig', + 'Remove-GSResource', + 'Get-GSGroup', + 'Get-GSChromeOSDevice', + 'Update-GSCustomer', + 'Update-GSAdminRole', + 'New-GSAdminRoleAssignment', + 'Get-GSCourseAlias', + 'Get-GSGroupMember', + 'Get-GSChatConfig', + 'Add-GSUserExternalId', + 'Get-GSCalendar', + 'Add-GSGroupMember', + 'Get-GSUserVerificationCodes', + 'New-GSUser', + 'Test-GSGroupMembership', + 'Add-GSEventAttendee', + 'Remove-GSPrincipalGroupMembership', + 'Get-GSAdminRoleAssignment', + 'Add-GSUserEmail', + 'Add-GSUserIm', + 'Update-GSGroupSettings', + 'Get-GSGmailImapSettings', + 'Get-GSAdminRole', + 'Add-GSChatImage', + 'Add-GSUserRelation', + 'Get-GSChatMember', + 'Send-GSGmailSendAsConfirmation', + 'New-GoogleService', + 'Remove-GSMobileDevice', + 'Get-GSDataTransferApplication', + 'Get-GSCustomer' + ) + return $Values + } +} diff --git a/PSGSuite/Class/PSGSuiteValidOAuthScopeValues.ps1 b/PSGSuite/Class/PSGSuiteValidOAuthScopeValues.ps1 new file mode 100644 index 00000000..8069a320 --- /dev/null +++ b/PSGSuite/Class/PSGSuiteValidOAuthScopeValues.ps1 @@ -0,0 +1,39 @@ +# Programmatically generated from template 'OAuthScopes.ps1' +# This file will be overwritten during the module build process. + +# Class that provides parameter validation for the list of OAuth scopes that are used by all PSGSuite functions. +class PSGSuiteValidOAuthScopeValues : System.Management.Automation.IValidateSetValuesGenerator { + [string[]] GetValidValues() { + $Values = @( + 'https://www.googleapis.com/auth/classroom.guardianlinks.students', + 'https://www.googleapis.com/auth/apps.licensing', + 'https://www.googleapis.com/auth/admin.directory.group', + 'https://www.googleapis.com/auth/admin.directory.domain', + 'https://www.googleapis.com/auth/gmail.settings.basic', + 'https://www.googleapis.com/auth/calendar', + 'https://www.googleapis.com/auth/drive', + 'https://www.googleapis.com/auth/admin.directory.resource.calendar', + 'https://www.googleapis.com/auth/admin.directory.rolemanagement', + 'https://www.googleapis.com/auth/admin.directory.user', + 'https://www.googleapis.com/auth/admin.directory.user.security', + 'https://www.googleapis.com/auth/gmail.settings.sharing', + 'https://www.googleapis.com/auth/classroom.courses', + 'https://www.googleapis.com/auth/classroom.rosters', + 'https://www.googleapis.com/auth/chat.bot', + 'https://www.googleapis.com/auth/admin.directory.user.readonly', + 'https://www.googleapis.com/auth/admin.directory.device.mobile', + 'https://www.googleapis.com/auth/admin.directory.orgunit', + 'https://www.googleapis.com/auth/admin.reports.usage.readonly', + 'https://www.googleapis.com/auth/admin.directory.userschema', + 'https://www.googleapis.com/auth/apps.groups.settings', + 'https://www.googleapis.com/auth/admin.reports.audit.readonly', + 'https://www.googleapis.com/auth/admin.datatransfer', + 'https://www.googleapis.com/auth/classroom.profile.emails', + 'https://www.googleapis.com/auth/classroom.profile.photos', + 'https://www.googleapis.com/auth/admin.directory.device.chromeos', + 'https://www.googleapis.com/auth/admin.directory.customer', + 'https://www.googleapis.com/auth/admin.directory.rolemanagement.readonly' + ) + return $Values + } +} diff --git a/PSGSuite/Class/PSGSuiteValidServiceValues.ps1 b/PSGSuite/Class/PSGSuiteValidServiceValues.ps1 new file mode 100644 index 00000000..b5b55002 --- /dev/null +++ b/PSGSuite/Class/PSGSuiteValidServiceValues.ps1 @@ -0,0 +1,23 @@ +# Programmatically generated from template 'OAuthScopes.ps1' +# This file will be overwritten during the module build process. + +# Class that provides parameter validation for the Google API services that are used by PSGSuite. +class PSGSuiteValidServiceValues : System.Management.Automation.IValidateSetValuesGenerator { + [string[]] GetValidValues() { + $Values = @( + 'Google.Apis.Classroom.v1.ClassroomService', + 'Google.Apis.Licensing.v1.LicensingService', + 'Google.Apis.Admin.Directory.directory_v1.DirectoryService', + 'Google.Apis.Gmail.v1.GmailService', + 'Google.Apis.Calendar.v3.CalendarService', + 'Google.Apis.Drive.v3.DriveService', + 'Google.Apis.Sheets.v4.SheetsService', + 'Google.Apis.HangoutsChat.v1.HangoutsChatService', + 'Google.Apis.Admin.Reports.reports_v1.ReportsService', + 'Google.Apis.Groupssettings.v1.GroupssettingsService', + 'Google.Apis.Admin.DataTransfer.datatransfer_v1.DataTransferService', + 'Google.Apis.Slides.v1.SlidesService' + ) + return $Values + } +} diff --git a/PSGSuite/Module/Aliases.ps1 b/PSGSuite/Module/Aliases.ps1 index 1329ba39..7b355218 100644 --- a/PSGSuite/Module/Aliases.ps1 +++ b/PSGSuite/Module/Aliases.ps1 @@ -45,6 +45,7 @@ $aliasHash = # Alias => => => => => => => => Function 'Update-GSGmailSendAsSettings' = 'Update-GSGmailSendAsAlias' 'Update-GSSheetValue' = 'Export-GSSheet' 'Update-GSTeamDrive' = 'Update-GSDrive' + 'Get-PSGSuiteScope' = 'Get-PSGSuiteOAuthScope' } foreach ($key in $aliasHash.Keys) { diff --git a/PSGSuite/Module/OAuthScopes.ps1 b/PSGSuite/Module/OAuthScopes.ps1 new file mode 100644 index 00000000..65d9b69c --- /dev/null +++ b/PSGSuite/Module/OAuthScopes.ps1 @@ -0,0 +1,1323 @@ +# Programmatically generated from template 'OAuthScopes.ps1' +# This file will be overwritten during the module build process. + +# Scope data that is used by the Get-PSGSuiteOAuthScope function. +$script:_PSGSuiteOAuthScopes = @' +[ + { + "Function": "Remove-GSStudentGuardian", + "Service": "Google.Apis.Classroom.v1.ClassroomService", + "Scope": "https://www.googleapis.com/auth/classroom.guardianlinks.students" + }, + { + "Function": "Remove-GSUserLicense", + "Service": "Google.Apis.Licensing.v1.LicensingService", + "Scope": "https://www.googleapis.com/auth/apps.licensing" + }, + { + "Function": "New-GSGroup", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.group" + }, + { + "Function": "New-GSDomainAlias", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.domain" + }, + { + "Function": "Update-GSGmailImapSettings", + "Service": "Google.Apis.Gmail.v1.GmailService", + "Scope": "https://www.googleapis.com/auth/gmail.settings.basic" + }, + { + "Function": "Get-GSCalendarACL", + "Service": "Google.Apis.Calendar.v3.CalendarService", + "Scope": "https://www.googleapis.com/auth/calendar" + }, + { + "Function": "Unblock-CoreCLREncryptionWarning", + "Service": null, + "Scope": null + }, + { + "Function": "Start-GSDriveFileUpload", + "Service": "Google.Apis.Drive.v3.DriveService", + "Scope": "https://www.googleapis.com/auth/drive" + }, + { + "Function": "Show-PSGSuiteConfig", + "Service": null, + "Scope": null + }, + { + "Function": "Remove-GSDrivePermission", + "Service": "Google.Apis.Drive.v3.DriveService", + "Scope": "https://www.googleapis.com/auth/drive" + }, + { + "Function": "Remove-GSGroup", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.group" + }, + { + "Function": "Update-GSResource", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.resource.calendar" + }, + { + "Function": "Set-GSDocContent", + "Service": "Google.Apis.Drive.v3.DriveService", + "Scope": "https://www.googleapis.com/auth/drive" + }, + { + "Function": "Update-GSDriveRevision", + "Service": "Google.Apis.Drive.v3.DriveService", + "Scope": "https://www.googleapis.com/auth/drive" + }, + { + "Function": "Remove-GSGroupAlias", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.group" + }, + { + "Function": "Clear-GSSheet", + "Service": "Google.Apis.Sheets.v4.SheetsService", + "Scope": "https://www.googleapis.com/auth/drive" + }, + { + "Function": "Remove-GSAdminRole", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.rolemanagement" + }, + { + "Function": "New-GSDriveFile", + "Service": "Google.Apis.Drive.v3.DriveService", + "Scope": "https://www.googleapis.com/auth/drive" + }, + { + "Function": "Get-GSGroupAlias", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.group" + }, + { + "Function": "Get-GSUserLicense", + "Service": "Google.Apis.Licensing.v1.LicensingService", + "Scope": "https://www.googleapis.com/auth/apps.licensing" + }, + { + "Function": "Update-GSSheet", + "Service": "Google.Apis.Sheets.v4.SheetsService", + "Scope": "https://www.googleapis.com/auth/drive" + }, + { + "Function": "Get-GSUserToken", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.user" + }, + { + "Function": "Get-GSUserToken", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.user.security" + }, + { + "Function": "Get-GSGmailVacationSettings", + "Service": "Google.Apis.Gmail.v1.GmailService", + "Scope": "https://www.googleapis.com/auth/gmail.settings.basic" + }, + { + "Function": "Remove-GSDriveRevision", + "Service": "Google.Apis.Drive.v3.DriveService", + "Scope": "https://www.googleapis.com/auth/drive" + }, + { + "Function": "Update-GSUserPhoto", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.user" + }, + { + "Function": "Remove-GSUserPhoto", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.user" + }, + { + "Function": "Remove-GSGmailDelegate", + "Service": "Google.Apis.Gmail.v1.GmailService", + "Scope": "https://www.googleapis.com/auth/gmail.settings.sharing" + }, + { + "Function": "Get-GSDriveFile", + "Service": "Google.Apis.Drive.v3.DriveService", + "Scope": "https://www.googleapis.com/auth/drive" + }, + { + "Function": "Update-GSCalendarEvent", + "Service": "Google.Apis.Calendar.v3.CalendarService", + "Scope": "https://www.googleapis.com/auth/calendar" + }, + { + "Function": "New-GSCourse", + "Service": "Google.Apis.Classroom.v1.ClassroomService", + "Scope": "https://www.googleapis.com/auth/classroom.courses" + }, + { + "Function": "Remove-GSCalendarEvent", + "Service": "Google.Apis.Calendar.v3.CalendarService", + "Scope": "https://www.googleapis.com/auth/calendar" + }, + { + "Function": "Remove-GSDriveFile", + "Service": "Google.Apis.Drive.v3.DriveService", + "Scope": "https://www.googleapis.com/auth/drive" + }, + { + "Function": "Add-GSCalendarEventReminder", + "Service": null, + "Scope": null + }, + { + "Function": "Remove-GSUserAlias", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.user" + }, + { + "Function": "Add-GSPrincipalGroupMembership", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.group" + }, + { + "Function": "Remove-GSCalendarAcl", + "Service": "Google.Apis.Calendar.v3.CalendarService", + "Scope": "https://www.googleapis.com/auth/calendar" + }, + { + "Function": "Update-GSGmailPopSettings", + "Service": "Google.Apis.Gmail.v1.GmailService", + "Scope": "https://www.googleapis.com/auth/gmail.settings.basic" + }, + { + "Function": "Get-GSCourseInvitation", + "Service": "Google.Apis.Classroom.v1.ClassroomService", + "Scope": "https://www.googleapis.com/auth/classroom.rosters" + }, + { + "Function": "Remove-GSUserASP", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.user" + }, + { + "Function": "Remove-GSUserASP", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.user.security" + }, + { + "Function": "Remove-GSAdminRoleAssignment", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.rolemanagement" + }, + { + "Function": "Add-GSUserAddress", + "Service": null, + "Scope": null + }, + { + "Function": "Get-GSChatSpace", + "Service": "Google.Apis.HangoutsChat.v1.HangoutsChatService", + "Scope": "https://www.googleapis.com/auth/chat.bot" + }, + { + "Function": "Get-GSChatSpace", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.user" + }, + { + "Function": "Get-GSChatSpace", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.user.readonly" + }, + { + "Function": "Block-CoreCLREncryptionWarning", + "Service": null, + "Scope": null + }, + { + "Function": "New-GSGroupAlias", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.group" + }, + { + "Function": "Send-GSChatMessage", + "Service": "Google.Apis.HangoutsChat.v1.HangoutsChatService", + "Scope": "https://www.googleapis.com/auth/chat.bot" + }, + { + "Function": "Update-GSMobileDevice", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.device.mobile" + }, + { + "Function": "Update-GSGroupMember", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.group" + }, + { + "Function": "Export-GSDriveFile", + "Service": "Google.Apis.Drive.v3.DriveService", + "Scope": "https://www.googleapis.com/auth/drive" + }, + { + "Function": "Get-GSDriveRevision", + "Service": "Google.Apis.Drive.v3.DriveService", + "Scope": "https://www.googleapis.com/auth/drive" + }, + { + "Function": "Get-GSDriveFileUploadStatus", + "Service": null, + "Scope": null + }, + { + "Function": "Watch-GSDriveUpload", + "Service": null, + "Scope": null + }, + { + "Function": "Get-GSChatMessage", + "Service": "Google.Apis.HangoutsChat.v1.HangoutsChatService", + "Scope": "https://www.googleapis.com/auth/chat.bot" + }, + { + "Function": "Get-GSOrganizationalUnit", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.orgunit" + }, + { + "Function": "New-GSPresentationUpdateRequest", + "Service": null, + "Scope": null + }, + { + "Function": "Import-GSSheet", + "Service": "Google.Apis.Sheets.v4.SheetsService", + "Scope": "https://www.googleapis.com/auth/drive" + }, + { + "Function": "New-GSGmailSendAsAlias", + "Service": "Google.Apis.Gmail.v1.GmailService", + "Scope": "https://www.googleapis.com/auth/gmail.settings.sharing" + }, + { + "Function": "Update-GSDrive", + "Service": "Google.Apis.Drive.v3.DriveService", + "Scope": "https://www.googleapis.com/auth/drive" + }, + { + "Function": "Remove-GSOrganizationalUnit", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.orgunit" + }, + { + "Function": "Copy-GSDriveFile", + "Service": "Google.Apis.Drive.v3.DriveService", + "Scope": "https://www.googleapis.com/auth/drive" + }, + { + "Function": "Remove-GSGmailSendAsAlias", + "Service": "Google.Apis.Gmail.v1.GmailService", + "Scope": "https://www.googleapis.com/auth/gmail.settings.sharing" + }, + { + "Function": "Restore-GSUser", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.user" + }, + { + "Function": "Restore-GSUser", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.user.readonly" + }, + { + "Function": "New-GSGmailSMIMEInfo", + "Service": "Google.Apis.Gmail.v1.GmailService", + "Scope": "https://www.googleapis.com/auth/gmail.settings.basic" + }, + { + "Function": "New-GSGmailSMIMEInfo", + "Service": "Google.Apis.Gmail.v1.GmailService", + "Scope": "https://www.googleapis.com/auth/gmail.settings.sharing" + }, + { + "Function": "Update-GSGroup", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.group" + }, + { + "Function": "Get-GSGmailAutoForwardingSettings", + "Service": "Google.Apis.Gmail.v1.GmailService", + "Scope": "https://www.googleapis.com/auth/gmail.settings.basic" + }, + { + "Function": "Add-GSChatTextParagraph", + "Service": null, + "Scope": null + }, + { + "Function": "Get-GSUserAlias", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.user" + }, + { + "Function": "Get-GSUserAlias", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.user.readonly" + }, + { + "Function": "Remove-GSChatMessage", + "Service": "Google.Apis.HangoutsChat.v1.HangoutsChatService", + "Scope": "https://www.googleapis.com/auth/chat.bot" + }, + { + "Function": "Add-GSChatOnClick", + "Service": null, + "Scope": null + }, + { + "Function": "Update-GSGmailVacationSettings", + "Service": "Google.Apis.Gmail.v1.GmailService", + "Scope": "https://www.googleapis.com/auth/gmail.settings.basic" + }, + { + "Function": "Remove-GSGmailFilter", + "Service": "Google.Apis.Gmail.v1.GmailService", + "Scope": "https://www.googleapis.com/auth/gmail.settings.basic" + }, + { + "Function": "Add-GSChatKeyValue", + "Service": null, + "Scope": null + }, + { + "Function": "Get-GSUsageReport", + "Service": "Google.Apis.Admin.Reports.reports_v1.ReportsService", + "Scope": "https://www.googleapis.com/auth/admin.reports.usage.readonly" + }, + { + "Function": "Get-GSUserSchema", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.user" + }, + { + "Function": "Get-GSUserSchema", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.userschema" + }, + { + "Function": "Get-GSGmailPopSettings", + "Service": "Google.Apis.Gmail.v1.GmailService", + "Scope": "https://www.googleapis.com/auth/gmail.settings.basic" + }, + { + "Function": "New-GSDrive", + "Service": "Google.Apis.Drive.v3.DriveService", + "Scope": "https://www.googleapis.com/auth/drive" + }, + { + "Function": "Add-GSCustomerPostalAddress", + "Service": null, + "Scope": null + }, + { + "Function": "Update-GSUserSchema", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.user" + }, + { + "Function": "Update-GSUserSchema", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.userschema" + }, + { + "Function": "Get-GSDomain", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.domain" + }, + { + "Function": "Get-GSGroupSettings", + "Service": "Google.Apis.Groupssettings.v1.GroupssettingsService", + "Scope": "https://www.googleapis.com/auth/apps.groups.settings" + }, + { + "Function": "Get-GSGroupSettings", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.group" + }, + { + "Function": "Get-GSGmailFilter", + "Service": "Google.Apis.Gmail.v1.GmailService", + "Scope": "https://www.googleapis.com/auth/gmail.settings.basic" + }, + { + "Function": "Get-GSStudentGuardian", + "Service": "Google.Apis.Classroom.v1.ClassroomService", + "Scope": "https://www.googleapis.com/auth/classroom.guardianlinks.students" + }, + { + "Function": "Update-GSGmailSendAsAlias", + "Service": "Google.Apis.Gmail.v1.GmailService", + "Scope": "https://www.googleapis.com/auth/gmail.settings.basic" + }, + { + "Function": "Update-GSGmailSendAsAlias", + "Service": "Google.Apis.Gmail.v1.GmailService", + "Scope": "https://www.googleapis.com/auth/gmail.settings.sharing" + }, + { + "Function": "Add-GSDocContent", + "Service": "Google.Apis.Drive.v3.DriveService", + "Scope": "https://www.googleapis.com/auth/drive" + }, + { + "Function": "Remove-GSUserToken", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.user" + }, + { + "Function": "Remove-GSUserToken", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.user.security" + }, + { + "Function": "Get-GSCourse", + "Service": "Google.Apis.Classroom.v1.ClassroomService", + "Scope": "https://www.googleapis.com/auth/classroom.courses" + }, + { + "Function": "Clear-PSGSuiteServiceCache", + "Service": null, + "Scope": null + }, + { + "Function": "Get-GSGmailSendAsAlias", + "Service": "Google.Apis.Gmail.v1.GmailService", + "Scope": "https://www.googleapis.com/auth/gmail.settings.basic" + }, + { + "Function": "Set-PSGSuiteConfig", + "Service": null, + "Scope": null + }, + { + "Function": "Add-GSCalendarSubscription", + "Service": "Google.Apis.Calendar.v3.CalendarService", + "Scope": "https://www.googleapis.com/auth/calendar" + }, + { + "Function": "Import-PSGSuiteConfig", + "Service": null, + "Scope": null + }, + { + "Function": "New-GSUserSchema", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.user" + }, + { + "Function": "New-GSUserSchema", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.userschema" + }, + { + "Function": "Get-GSDriveFolderSize", + "Service": "Google.Apis.Drive.v3.DriveService", + "Scope": "https://www.googleapis.com/auth/drive" + }, + { + "Function": "Invoke-GSUserOffboarding", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.user" + }, + { + "Function": "Invoke-GSUserOffboarding", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.user.security" + }, + { + "Function": "Invoke-GSUserOffboarding", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.user.readonly" + }, + { + "Function": "Invoke-GSUserOffboarding", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.device.mobile" + }, + { + "Function": "Invoke-GSUserOffboarding", + "Service": "Google.Apis.Licensing.v1.LicensingService", + "Scope": "https://www.googleapis.com/auth/apps.licensing" + }, + { + "Function": "Remove-GSDrive", + "Service": "Google.Apis.Drive.v3.DriveService", + "Scope": "https://www.googleapis.com/auth/drive" + }, + { + "Function": "Add-GSChatCard", + "Service": null, + "Scope": null + }, + { + "Function": "Update-GSCourse", + "Service": "Google.Apis.Classroom.v1.ClassroomService", + "Scope": "https://www.googleapis.com/auth/classroom.courses" + }, + { + "Function": "Remove-GSCourseParticipant", + "Service": "Google.Apis.Classroom.v1.ClassroomService", + "Scope": "https://www.googleapis.com/auth/classroom.rosters" + }, + { + "Function": "Get-GSDrivePermission", + "Service": "Google.Apis.Drive.v3.DriveService", + "Scope": "https://www.googleapis.com/auth/drive" + }, + { + "Function": "Get-GSActivityReport", + "Service": "Google.Apis.Admin.Reports.reports_v1.ReportsService", + "Scope": "https://www.googleapis.com/auth/admin.reports.audit.readonly" + }, + { + "Function": "Set-GSUserSchema", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.user" + }, + { + "Function": "Set-GSUserSchema", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.userschema" + }, + { + "Function": "Get-GSGmailLanguageSettings", + "Service": "Google.Apis.Gmail.v1.GmailService", + "Scope": "https://www.googleapis.com/auth/gmail.settings.basic" + }, + { + "Function": "Revoke-GSStudentGuardianInvitation", + "Service": "Google.Apis.Classroom.v1.ClassroomService", + "Scope": "https://www.googleapis.com/auth/classroom.guardianlinks.students" + }, + { + "Function": "Get-GSUserASP", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.user" + }, + { + "Function": "Get-GSUserASP", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.user.security" + }, + { + "Function": "Get-GSDrive", + "Service": "Google.Apis.Drive.v3.DriveService", + "Scope": "https://www.googleapis.com/auth/drive" + }, + { + "Function": "Add-GSUserPhone", + "Service": null, + "Scope": null + }, + { + "Function": "Set-GSUserLicense", + "Service": "Google.Apis.Licensing.v1.LicensingService", + "Scope": "https://www.googleapis.com/auth/apps.licensing" + }, + { + "Function": "Get-GSResource", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.resource.calendar" + }, + { + "Function": "New-GSUserVerificationCodes", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.user" + }, + { + "Function": "New-GSUserVerificationCodes", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.user.security" + }, + { + "Function": "Set-GSGroupSettings", + "Service": "Google.Apis.Groupssettings.v1.GroupssettingsService", + "Scope": "https://www.googleapis.com/auth/apps.groups.settings" + }, + { + "Function": "New-GSDomain", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.domain" + }, + { + "Function": "Get-PSGSuiteConfig", + "Service": null, + "Scope": null + }, + { + "Function": "New-GSCalendarACL", + "Service": "Google.Apis.Calendar.v3.CalendarService", + "Scope": "https://www.googleapis.com/auth/calendar" + }, + { + "Function": "Add-GSCourseParticipant", + "Service": "Google.Apis.Classroom.v1.ClassroomService", + "Scope": "https://www.googleapis.com/auth/classroom.rosters" + }, + { + "Function": "Get-GSMobileDevice", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.device.mobile" + }, + { + "Function": "Remove-GSGroupMember", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.group" + }, + { + "Function": "Get-GSStudentGuardianInvitation", + "Service": "Google.Apis.Classroom.v1.ClassroomService", + "Scope": "https://www.googleapis.com/auth/classroom.guardianlinks.students" + }, + { + "Function": "Get-GSSheetInfo", + "Service": "Google.Apis.Sheets.v4.SheetsService", + "Scope": "https://www.googleapis.com/auth/drive" + }, + { + "Function": "Get-GSDocContent", + "Service": "Google.Apis.Drive.v3.DriveService", + "Scope": "https://www.googleapis.com/auth/drive" + }, + { + "Function": "Start-GSDataTransfer", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.user" + }, + { + "Function": "Start-GSDataTransfer", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.user.readonly" + }, + { + "Function": "Start-GSDataTransfer", + "Service": "Google.Apis.Admin.DataTransfer.datatransfer_v1.DataTransferService", + "Scope": "https://www.googleapis.com/auth/admin.datatransfer" + }, + { + "Function": "Update-GSUser", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.user" + }, + { + "Function": "Update-GSUser", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.user.readonly" + }, + { + "Function": "Remove-GSUserSchema", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.user" + }, + { + "Function": "Remove-GSUserSchema", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.userschema" + }, + { + "Function": "Add-GSGmailForwardingAddress", + "Service": "Google.Apis.Gmail.v1.GmailService", + "Scope": "https://www.googleapis.com/auth/gmail.settings.sharing" + }, + { + "Function": "Get-GSDriveFileList", + "Service": "Google.Apis.Drive.v3.DriveService", + "Scope": "https://www.googleapis.com/auth/drive" + }, + { + "Function": "Add-GSGmailSmtpMsa", + "Service": null, + "Scope": null + }, + { + "Function": "Get-GSGmailSMIMEInfo", + "Service": "Google.Apis.Gmail.v1.GmailService", + "Scope": "https://www.googleapis.com/auth/gmail.settings.basic" + }, + { + "Function": "Add-GSGmailFilter", + "Service": "Google.Apis.Gmail.v1.GmailService", + "Scope": "https://www.googleapis.com/auth/gmail.settings.basic" + }, + { + "Function": "Update-GSGmailSignature", + "Service": "Google.Apis.Gmail.v1.GmailService", + "Scope": "https://www.googleapis.com/auth/gmail.settings.basic" + }, + { + "Function": "Update-GSGmailSignature", + "Service": "Google.Apis.Gmail.v1.GmailService", + "Scope": "https://www.googleapis.com/auth/gmail.settings.sharing" + }, + { + "Function": "Update-GSOrganizationalUnit", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.orgunit" + }, + { + "Function": "Compare-ModuleVersion", + "Service": null, + "Scope": null + }, + { + "Function": "New-GSAdminRole", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.rolemanagement" + }, + { + "Function": "Confirm-GSCourseInvitation", + "Service": "Google.Apis.Classroom.v1.ClassroomService", + "Scope": "https://www.googleapis.com/auth/classroom.rosters" + }, + { + "Function": "Get-GSCalendarEvent", + "Service": "Google.Apis.Calendar.v3.CalendarService", + "Scope": "https://www.googleapis.com/auth/calendar" + }, + { + "Function": "Remove-GSCourse", + "Service": "Google.Apis.Classroom.v1.ClassroomService", + "Scope": "https://www.googleapis.com/auth/classroom.courses" + }, + { + "Function": "Update-GSCalendarSubscription", + "Service": "Google.Apis.Calendar.v3.CalendarService", + "Scope": "https://www.googleapis.com/auth/calendar" + }, + { + "Function": "Sync-GSUserCache", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.user" + }, + { + "Function": "Sync-GSUserCache", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.user.readonly" + }, + { + "Function": "Add-GSUserOrganization", + "Service": null, + "Scope": null + }, + { + "Function": "Update-GSUserLicense", + "Service": "Google.Apis.Licensing.v1.LicensingService", + "Scope": "https://www.googleapis.com/auth/apps.licensing" + }, + { + "Function": "Update-GSChatMessage", + "Service": "Google.Apis.HangoutsChat.v1.HangoutsChatService", + "Scope": "https://www.googleapis.com/auth/chat.bot" + }, + { + "Function": "Get-GSDataTransfer", + "Service": "Google.Apis.Admin.DataTransfer.datatransfer_v1.DataTransferService", + "Scope": "https://www.googleapis.com/auth/admin.datatransfer" + }, + { + "Function": "New-GSCourseInvitation", + "Service": "Google.Apis.Classroom.v1.ClassroomService", + "Scope": "https://www.googleapis.com/auth/classroom.rosters" + }, + { + "Function": "Revoke-GSUserVerificationCodes", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.user" + }, + { + "Function": "Revoke-GSUserVerificationCodes", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.user.security" + }, + { + "Function": "Stop-GSDriveFileUpload", + "Service": null, + "Scope": null + }, + { + "Function": "Remove-GSGmailSMIMEInfo", + "Service": "Google.Apis.Gmail.v1.GmailService", + "Scope": "https://www.googleapis.com/auth/gmail.settings.basic" + }, + { + "Function": "Edit-GSPresentation", + "Service": "Google.Apis.Slides.v1.SlidesService", + "Scope": "https://www.googleapis.com/auth/drive" + }, + { + "Function": "Edit-GSPresentation", + "Service": "Google.Apis.Drive.v3.DriveService", + "Scope": "https://www.googleapis.com/auth/drive" + }, + { + "Function": "Add-GSChatButton", + "Service": null, + "Scope": null + }, + { + "Function": "Get-GSClassroomUserProfile", + "Service": "Google.Apis.Classroom.v1.ClassroomService", + "Scope": "https://www.googleapis.com/auth/classroom.profile.emails" + }, + { + "Function": "Get-GSClassroomUserProfile", + "Service": "Google.Apis.Classroom.v1.ClassroomService", + "Scope": "https://www.googleapis.com/auth/classroom.profile.photos" + }, + { + "Function": "Get-GSClassroomUserProfile", + "Service": "Google.Apis.Classroom.v1.ClassroomService", + "Scope": "https://www.googleapis.com/auth/classroom.rosters" + }, + { + "Function": "Get-GSGmailDelegate", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.group" + }, + { + "Function": "Get-GSGmailDelegate", + "Service": "Google.Apis.Gmail.v1.GmailService", + "Scope": "https://www.googleapis.com/auth/gmail.settings.basic" + }, + { + "Function": "Remove-GSCourseAlias", + "Service": "Google.Apis.Classroom.v1.ClassroomService", + "Scope": "https://www.googleapis.com/auth/classroom.courses" + }, + { + "Function": "Remove-GSCourseInvitation", + "Service": "Google.Apis.Classroom.v1.ClassroomService", + "Scope": "https://www.googleapis.com/auth/classroom.rosters" + }, + { + "Function": "Get-GSCalendarSubscription", + "Service": "Google.Apis.Calendar.v3.CalendarService", + "Scope": "https://www.googleapis.com/auth/calendar" + }, + { + "Function": "Get-GSCourseParticipant", + "Service": "Google.Apis.Classroom.v1.ClassroomService", + "Scope": "https://www.googleapis.com/auth/classroom.profile.emails" + }, + { + "Function": "Get-GSCourseParticipant", + "Service": "Google.Apis.Classroom.v1.ClassroomService", + "Scope": "https://www.googleapis.com/auth/classroom.profile.photos" + }, + { + "Function": "Get-GSCourseParticipant", + "Service": "Google.Apis.Classroom.v1.ClassroomService", + "Scope": "https://www.googleapis.com/auth/classroom.rosters" + }, + { + "Function": "New-GSUserAlias", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.user" + }, + { + "Function": "Show-GSDrive", + "Service": "Google.Apis.Drive.v3.DriveService", + "Scope": "https://www.googleapis.com/auth/drive" + }, + { + "Function": "Copy-GSSheet", + "Service": "Google.Apis.Sheets.v4.SheetsService", + "Scope": "https://www.googleapis.com/auth/drive" + }, + { + "Function": "Get-GSPresentation", + "Service": "Google.Apis.Slides.v1.SlidesService", + "Scope": "https://www.googleapis.com/auth/drive" + }, + { + "Function": "Get-GSPresentation", + "Service": "Google.Apis.Drive.v3.DriveService", + "Scope": "https://www.googleapis.com/auth/drive" + }, + { + "Function": "Update-GSChromeOSDevice", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.device.chromeos" + }, + { + "Function": "Remove-GSCalendarSubscription", + "Service": "Google.Apis.Calendar.v3.CalendarService", + "Scope": "https://www.googleapis.com/auth/calendar" + }, + { + "Function": "Remove-GSUser", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.user" + }, + { + "Function": "New-GSOrganizationalUnit", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.orgunit" + }, + { + "Function": "Add-GSUserLocation", + "Service": null, + "Scope": null + }, + { + "Function": "Add-GSGmailDelegate", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.group" + }, + { + "Function": "Add-GSGmailDelegate", + "Service": "Google.Apis.Gmail.v1.GmailService", + "Scope": "https://www.googleapis.com/auth/gmail.settings.sharing" + }, + { + "Function": "Remove-GSDomainAlias", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.domain" + }, + { + "Function": "Update-GSDriveFile", + "Service": "Google.Apis.Drive.v3.DriveService", + "Scope": "https://www.googleapis.com/auth/drive" + }, + { + "Function": "Add-GSChatCardSection", + "Service": null, + "Scope": null + }, + { + "Function": "Add-GSCalendarNotification", + "Service": null, + "Scope": null + }, + { + "Function": "Update-GSGmailLanguageSettings", + "Service": "Google.Apis.Gmail.v1.GmailService", + "Scope": "https://www.googleapis.com/auth/gmail.settings.basic" + }, + { + "Function": "Add-GSUserSchemaField", + "Service": null, + "Scope": null + }, + { + "Function": "Get-GSGmailForwardingAddress", + "Service": "Google.Apis.Gmail.v1.GmailService", + "Scope": "https://www.googleapis.com/auth/gmail.settings.basic" + }, + { + "Function": "Add-GSSheetValues", + "Service": "Google.Apis.Sheets.v4.SheetsService", + "Scope": "https://www.googleapis.com/auth/drive" + }, + { + "Function": "Get-PSGSuiteServiceCache", + "Service": null, + "Scope": null + }, + { + "Function": "New-GSStudentGuardianInvitation", + "Service": "Google.Apis.Classroom.v1.ClassroomService", + "Scope": "https://www.googleapis.com/auth/classroom.guardianlinks.students" + }, + { + "Function": "New-GSCourseAlias", + "Service": "Google.Apis.Classroom.v1.ClassroomService", + "Scope": "https://www.googleapis.com/auth/classroom.courses" + }, + { + "Function": "Add-GSChatCardAction", + "Service": null, + "Scope": null + }, + { + "Function": "Remove-GSDomain", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.domain" + }, + { + "Function": "Get-GSDriveProfile", + "Service": "Google.Apis.Drive.v3.DriveService", + "Scope": "https://www.googleapis.com/auth/drive" + }, + { + "Function": "Hide-GSDrive", + "Service": "Google.Apis.Drive.v3.DriveService", + "Scope": "https://www.googleapis.com/auth/drive" + }, + { + "Function": "New-GSSheet", + "Service": "Google.Apis.Sheets.v4.SheetsService", + "Scope": "https://www.googleapis.com/auth/drive" + }, + { + "Function": "New-GSCalendarEvent", + "Service": "Google.Apis.Calendar.v3.CalendarService", + "Scope": "https://www.googleapis.com/auth/calendar" + }, + { + "Function": "Get-PSGSuiteOAuthScope", + "Service": null, + "Scope": null + }, + { + "Function": "Get-GSUserPhoto", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.user" + }, + { + "Function": "Get-GSUserPhoto", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.user.readonly" + }, + { + "Function": "Switch-PSGSuiteConfig", + "Service": null, + "Scope": null + }, + { + "Function": "Update-GSGmailAutoForwardingSettings", + "Service": "Google.Apis.Gmail.v1.GmailService", + "Scope": "https://www.googleapis.com/auth/gmail.settings.sharing" + }, + { + "Function": "Export-GSSheet", + "Service": "Google.Apis.Sheets.v4.SheetsService", + "Scope": "https://www.googleapis.com/auth/drive" + }, + { + "Function": "Get-GSUser", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.user" + }, + { + "Function": "Get-GSUser", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.user.readonly" + }, + { + "Function": "Add-GSDrivePermission", + "Service": "Google.Apis.Drive.v3.DriveService", + "Scope": "https://www.googleapis.com/auth/drive" + }, + { + "Function": "New-GSResource", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.resource.calendar" + }, + { + "Function": "Get-GSDomainAlias", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.domain" + }, + { + "Function": "Export-PSGSuiteConfig", + "Service": null, + "Scope": null + }, + { + "Function": "Remove-GSResource", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.resource.calendar" + }, + { + "Function": "Get-GSGroup", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.group" + }, + { + "Function": "Get-GSChromeOSDevice", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.device.chromeos" + }, + { + "Function": "Update-GSCustomer", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.customer" + }, + { + "Function": "Update-GSAdminRole", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.rolemanagement" + }, + { + "Function": "New-GSAdminRoleAssignment", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.rolemanagement" + }, + { + "Function": "New-GSAdminRoleAssignment", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.user" + }, + { + "Function": "New-GSAdminRoleAssignment", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.user.readonly" + }, + { + "Function": "Get-GSCourseAlias", + "Service": "Google.Apis.Classroom.v1.ClassroomService", + "Scope": "https://www.googleapis.com/auth/classroom.courses" + }, + { + "Function": "Get-GSGroupMember", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.group" + }, + { + "Function": "Get-GSChatConfig", + "Service": null, + "Scope": null + }, + { + "Function": "Add-GSUserExternalId", + "Service": null, + "Scope": null + }, + { + "Function": "Get-GSCalendar", + "Service": "Google.Apis.Calendar.v3.CalendarService", + "Scope": "https://www.googleapis.com/auth/calendar" + }, + { + "Function": "Add-GSGroupMember", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.group" + }, + { + "Function": "Get-GSUserVerificationCodes", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.user" + }, + { + "Function": "Get-GSUserVerificationCodes", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.user.security" + }, + { + "Function": "New-GSUser", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.user" + }, + { + "Function": "Test-GSGroupMembership", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.group" + }, + { + "Function": "Add-GSEventAttendee", + "Service": null, + "Scope": null + }, + { + "Function": "Remove-GSPrincipalGroupMembership", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.group" + }, + { + "Function": "Get-GSAdminRoleAssignment", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.rolemanagement" + }, + { + "Function": "Get-GSAdminRoleAssignment", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.rolemanagement.readonly" + }, + { + "Function": "Get-GSAdminRoleAssignment", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.user" + }, + { + "Function": "Get-GSAdminRoleAssignment", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.user.readonly" + }, + { + "Function": "Add-GSUserEmail", + "Service": null, + "Scope": null + }, + { + "Function": "Add-GSUserIm", + "Service": null, + "Scope": null + }, + { + "Function": "Update-GSGroupSettings", + "Service": "Google.Apis.Groupssettings.v1.GroupssettingsService", + "Scope": "https://www.googleapis.com/auth/apps.groups.settings" + }, + { + "Function": "Update-GSGroupSettings", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.group" + }, + { + "Function": "Get-GSGmailImapSettings", + "Service": "Google.Apis.Gmail.v1.GmailService", + "Scope": "https://www.googleapis.com/auth/gmail.settings.basic" + }, + { + "Function": "Get-GSAdminRole", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.rolemanagement" + }, + { + "Function": "Add-GSChatImage", + "Service": null, + "Scope": null + }, + { + "Function": "Add-GSUserRelation", + "Service": null, + "Scope": null + }, + { + "Function": "Get-GSChatMember", + "Service": "Google.Apis.HangoutsChat.v1.HangoutsChatService", + "Scope": "https://www.googleapis.com/auth/chat.bot" + }, + { + "Function": "Send-GSGmailSendAsConfirmation", + "Service": "Google.Apis.Gmail.v1.GmailService", + "Scope": "https://www.googleapis.com/auth/gmail.settings.sharing" + }, + { + "Function": "New-GoogleService", + "Service": null, + "Scope": null + }, + { + "Function": "Remove-GSMobileDevice", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.device.mobile" + }, + { + "Function": "Get-GSDataTransferApplication", + "Service": "Google.Apis.Admin.DataTransfer.datatransfer_v1.DataTransferService", + "Scope": "https://www.googleapis.com/auth/admin.datatransfer" + }, + { + "Function": "Get-GSCustomer", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.customer" + } +] +'@ | ConvertFrom-Json diff --git a/PSGSuite/Public/Authentication/Get-PSGSuiteOAuthScope.ps1 b/PSGSuite/Public/Authentication/Get-PSGSuiteOAuthScope.ps1 new file mode 100644 index 00000000..ee50656e --- /dev/null +++ b/PSGSuite/Public/Authentication/Get-PSGSuiteOAuthScope.ps1 @@ -0,0 +1,152 @@ +Function Get-PSGSuiteOAuthScope { + <# + .SYNOPSIS + Returns the OAuth scopes used by PSGSuite + + .DESCRIPTION + Returns the OAuth scopes used by PSGSuite + + .OUTPUTS + [PSObject[]] + + By default each result is returned as a PSObject that contains the following properties: + - Function - PSGSuite function that uses the scope + - Service - API service that owns the scope + - Scope - Scope value + + .OUTPUTS + [String[]] + + It is possible to return the list of unique scope values by using the -ValueOnly parameter + + .PARAMETER Scope + Filters the output for the specified scope + + .PARAMETER Service + Filters the output for the specified service + + .PARAMETER Function + Filters the output for the specified function + + .PARAMETER ValueOnly + Returns the unique scope values only + + .EXAMPLE + PS > Get-PSGSuiteOAuthScope -Service Google.Apis.Slides.v1.SlidesService + + Function Service Scope + -------- ------- ----- + Get-GSPresentation Google.Apis.Slides.v1.SlidesService https://www.googleapis.com/auth/drive + Edit-GSPresentation Google.Apis.Slides.v1.SlidesService https://www.googleapis.com/auth/drive + + .EXAMPLE + PS > Get-PSGSuiteOAuthScope -Function 'Get-GSUser' -ValueOnly + + https://www.googleapis.com/auth/admin.directory.user + https://www.googleapis.com/auth/admin.directory.user.readonly + + .EXAMPLE + PS > Get-PSGSuiteOAuthScope -Scope https://www.googleapis.com/auth/chat.bot + + Function Service Scope + -------- ------- ----- + Send-GSChatMessage Google.Apis.HangoutsChat.v1.HangoutsChatService https://www.googleapis.com/auth/chat.bot + Get-GSChatMessage Google.Apis.HangoutsChat.v1.HangoutsChatService https://www.googleapis.com/auth/chat.bot + Remove-GSChatMessage Google.Apis.HangoutsChat.v1.HangoutsChatService https://www.googleapis.com/auth/chat.bot + Get-GSChatSpace Google.Apis.HangoutsChat.v1.HangoutsChatService https://www.googleapis.com/auth/chat.bot + Update-GSChatMessage Google.Apis.HangoutsChat.v1.HangoutsChatService https://www.googleapis.com/auth/chat.bot + Get-GSChatMember Google.Apis.HangoutsChat.v1.HangoutsChatService https://www.googleapis.com/auth/chat.bot + + .LINK + https://psgsuite.io/Function%20Help/Authentication/Get-PSGSuiteOAuthScope/ + + .LINK + https://developers.google.com/identity/protocols/oauth2/scopes + + #> + + [CmdletBinding(DefaultParameterSetName='GetAll')] + [OutputType([Object[]])] + param( + [Parameter(Mandatory=$true, ParameterSetName='GetService')] + [ValidateSet([PSGSuiteValidServiceValues])] + [String]$Service, + + [Parameter(Mandatory=$true, ParameterSetName='GetFunction')] + [ValidateSet([PSGSuiteValidFunctionValues])] + [String]$Function, + + [Parameter(Mandatory=$true, ParameterSetName='GetScope')] + [ValidateSet([PSGSuiteValidOAuthScopeValues])] + [String]$Scope, + + [Parameter(Mandatory=$false, ParameterSetName='GetAll')] + [Parameter(Mandatory=$false, ParameterSetName='GetService')] + [Parameter(Mandatory=$false, ParameterSetName='GetFunction')] + [Parameter(Mandatory=$false, ParameterSetName='GetScope')] + [Switch]$ValueOnly + ) + + begin { + If (-not $Script:_PSGSuiteOAuthScopes){ + $PSCmdlet.ThrowTerminatingError((ThrowTerm "The PSGSuite scopes were not found")) + } + } + + process { + + Switch ($PSCmdlet.ParameterSetName){ + + 'GetService' { + If ($ValueOnly){ + Write-Verbose "Getting all unique scope values objects for service '$Service'" + $script:_PSGSuiteOAuthScopes | Where-Object {$_.Service -eq $Service} | Select-Object -ExpandProperty 'Scope' -Unique + } else { + Write-Verbose "Getting all scope objects for service '$Service'" + $script:_PSGSuiteOAuthScopes | Where-Object {$_.Service -eq $Service} | ForEach-Object { + $_.psobject.Copy() + } + } + } + + 'GetFunction' { + If ($ValueOnly){ + Write-Verbose "Getting all unique scope values for function '$Function'" + $script:_PSGSuiteOAuthScopes | Where-Object {$_.Function -eq $Function} | Select-Object -ExpandProperty 'Scope' -Unique + } else { + Write-Verbose "Getting all scope objects for function '$Function'" + $script:_PSGSuiteOAuthScopes | Where-Object {$_.Function -eq $Function} | ForEach-Object { + $_.psobject.Copy() + } + } + } + + 'GetScope' { + If ($ValueOnly){ + Write-Verbose "Getting all unique scope values for scope '$Scope'" + $script:_PSGSuiteOAuthScopes | Where-Object {$_.Scope -eq $Scope} | Select-Object -ExpandProperty 'Scope' -Unique + } else { + Write-Verbose "Getting all scope objects for scope '$Scope'" + $script:_PSGSuiteOAuthScopes | Where-Object {$_.Scope -eq $Scope} | ForEach-Object { + $_.psobject.Copy() + } + } + } + + 'GetAll' { + If ($ValueOnly){ + Write-Verbose "Getting all unique scope values" + $script:_PSGSuiteOAuthScopes | Select-Object -ExpandProperty 'Scope' -Unique + } else { + Write-Verbose "Getting all scope objects" + $script:_PSGSuiteOAuthScopes | ForEach-Object { + $_.psobject.Copy() + } + } + } + + } + + } + +} \ No newline at end of file diff --git a/ci/templates/OAuthScopes.ps1 b/ci/templates/OAuthScopes.ps1 new file mode 100644 index 00000000..eede2733 --- /dev/null +++ b/ci/templates/OAuthScopes.ps1 @@ -0,0 +1,251 @@ +Param( + [parameter(mandatory=$true)]$SourceDirectory +) + + +<# + This template searches all public/private functions for the Google API services and OAuth Scopes that are used. + + It is assumed that each PSGSuite function has a 1:1 mapping to a .ps1 file, and the .ps1 file has the same name as the function. + + Each private/public function is checked for API services and OAuth scopes by: + 1. The function file is searched for any API service strings. + 2. If an API service string is found, all OAuth scopes are retrieved from the service object. + 3. The function file is searched for all OAuth scopes from the API service and any matches are recorded. + 4. The function file is then searched for any references to dependent PSGSuite functions. + 4. If a reference to another PSGSuite function is found, that functionm file is also checked for scopes if not already checked. + 5. If any API services or OAuth scopes were found for the referenced function, they are copied to the current function. + + The items found for all public functions are used to generate the PowerShell code that defines the following items: + - $script:_PSGSuiteOAuthScopes - This is an array that contains all items that were found. + - class [PSGSuiteValidServiceValues] - This is a parameter validation class that contains all Google API services that were found. + - class [PSGSuiteValidFunctionValues] - This is a parameter validation class that contains all of the public PSGSuite function names. + - class [PSGSuiteValidOAuthScopeValues] - This is a parameter validation class that contains all of the OAuth scopes that were found. + + The generated PowerShell code is output as a string to the pipeline so that it can be injected into the compiled module. +#> + + +Write-BuildLog "[OAuthScopes.ps1] Searching all PSGSuite functions for OAuth scopes." +Write-BuildLog "[OAuthScopes.ps1] Results are shown in format 'Unique (Found/Inherited)'" + +# This should contain files that are not to be resolved for their scopes. +# Values are to be the file name exlcuding the '.ps1' extension. This should also be the function name +# as it is assumed that each file contains exactly one function. +$Script:ExcludeFunctions = @( + 'Get-GSShortUrlListPrivate' +) + +Function Resolve-FunctionScopes { + Param( + [Parameter(Mandatory=$True)] + [String]$FunctionName, + [Parameter(Mandatory=$False)] + [string[]]$NestedCalls = @() + ) + + # Check if function already resolved + If ($Script:FunctionScopes.ContainsKey($FunctionName)){ + Return + } + + $NestedCalls += $FunctionName + $FunctionPath = $Script:FunctionPaths[$FunctionName] + $DirectorySeperator = [Regex]::Escape([System.IO.Path]::DirectorySeparatorChar) + + $FriendlyPath = $FunctionPath -replace "^(.+$DirectorySeperator)(?(Private|Public)($DirectorySeperator[A-Za-z0-9- ]+)+?\.ps1)$",'${friendlyPath}' + Write-BuildLog "[OAuthScopes.ps1] [$($NestedCalls.Count)] [$FunctionName] Resolving OAuth scopes in function '$FriendlyPath'" + + $Script:FunctionScopes[$FunctionName] = @{} + + $FunctionContent = [System.IO.File]::ReadAllText($FunctionPath) + # Remove the comments, they may contain false positives + $FunctionContent = $FunctionContent -replace "(?s)<#.+#>", "" + $FunctionContent = $FunctionContent -replace "(?m)#.+$", "" + + # Find any Google API service strings in the file + $MatchedServicesCount = 0 + $MatchedScopesCount = 0 + $Results = $FunctionContent | Select-String -Pattern "['`"](?Google\.Apis(\.[a-zA-Z0-9_]+){3,4}Service)['`"]" -AllMatches + If ($Results){ + $MatchedServiceStrings = @() + ForEach ($Match in $Results.matches){ + $MatchedServiceStrings += $Match.Groups['service'].value + } + ForEach ($ServiceString in ($MatchedServiceStrings | Select-Object -Unique)){ + + $MatchedServicesCount++ + + If (-not $Script:FunctionScopes[$FunctionName].ContainsKey($ServiceString)){ + $Script:FunctionScopes[$FunctionName][$ServiceString] = [System.Collections.Generic.HashSet[String]]::new() + } + + # Search the file for scope strings that belong to the service. + # https://github.com/googleapis/google-api-dotnet-client/issues/367 + $ServiceScopes = ([Type]"$ServiceString+ScopeConstants").DeclaredFields.GetRawConstantValue() + ForEach ($ScopeString in $ServiceScopes){ + If ($FunctionContent -match $ScopeString){ + $MatchedScopesCount++ + $Script:FunctionScopes[$FunctionName][$ServiceString].add($ScopeString) | Out-Null + } + } + + } + + } + + # Find any referenced PSGSuite functions in the file and inherit their scopes. + $CopiedServicesCount = 0 + $CopiedScopesCount = 0 + ForEach ($PSGSuiteFunctionName in $Script:FunctionPaths.keys){ + + # Don't search excluded files + If ($Script:ExcludeFunctions -contains $PSGSuiteFunctionName){ + Continue + } + + # Don't recursively search the current function + If ($PSGSuiteFunctionName -eq $FunctionName){ + Continue + } + + # Find any references to the function, skip if none found + If ($FunctionContent -notmatch "$([Regex]::Escape($PSGSuiteFunctionName))[\s]"){ + Continue + } + + # Check for a recursive loop. + If ($NestedCalls -contains $PSGSuiteFunctionName){ + Write-BuildLog "[OAuthScopes.ps1] [$($NestedCalls.Count)] [$FunctionName] Recursive reference for function '$PSGSuiteFunctionName' detected: $($NestedCalls -Join " --> ")" -warning + Continue + } + + # Resolve the function's scopes, if neccessary + If (-not $Script:FunctionScopes.ContainsKey($PSGSuiteFunctionName)){ + Resolve-FunctionScopes -FunctionName $PSGSuiteFunctionName -NestedCalls $NestedCalls + } + + # copy the scopes + ForEach ($ServiceKey in $Script:FunctionScopes[$PSGSuiteFunctionName].Keys){ + $CopiedServicesCount++ + If (-not $Script:FunctionScopes[$FunctionName].containsKey($ServiceKey)){ + $Script:FunctionScopes[$FunctionName][$ServiceKey] = [System.Collections.Generic.HashSet[String]]::new() + } + ForEach ($ScopeString in $Script:FunctionScopes[$PSGSuiteFunctionName][$ServiceKey]){ + $Script:FunctionScopes[$FunctionName][$ServiceKey].add($ScopeString) | Out-Null + $CopiedScopesCount++ + } + } + } + + $UniqueScopesCount = ($Script:FunctionScopes[$FunctionName].values.count | Measure-Object -Sum).Sum + Write-BuildLog "[OAuthScopes.ps1] [$($NestedCalls.Count)] [$FunctionName] Results - Services: $($Script:FunctionScopes[$FunctionName].Count) ($MatchedServicesCount/$CopiedServicesCount); Scopes: $UniqueScopesCount ($MatchedScopesCount/$CopiedScopesCount)" + +} + +$Script:FunctionScopes = @{} +$Script:FunctionPaths = @{} + +# get all function files +foreach ($Scope in @('Private', 'Public')) { + $functionsDirectory = Join-Path (Join-Path $SourceDirectory 'PSGSuite') $Scope + Get-ChildItem -Path $FunctionsDirectory -Filter '*.ps1' -Recurse -File | Where-Object {$Script:ExcludeFunctions -notcontains $_.BaseName} | ForEach-Object { + $Script:FunctionPaths[$_.BaseName] = $_.FullName + } +} + +# Resolve scopes in each file +foreach ($FunctionName in $FunctionPaths.Keys) { + Resolve-FunctionScopes -FunctionName $FunctionName +} + +# Filter for public functions only and generate the output hashtables +$PublicFunctionsDirectory = [Regex]::Escape((Join-Path (Join-Path $SourceDirectory 'PSGSuite') 'Public')) +$OutputScopes = @() +ForEach ($FunctionName in $Script:FunctionScopes.keys){ + If ($Script:FunctionPaths[$FunctionName] -match $PublicFunctionsDirectory){ + ForEach ($ServiceKey in $Script:FunctionScopes[$FunctionName].keys){ + ForEach ($ScopeString in $Script:FunctionScopes[$FunctionName][$ServiceKey].GetEnumerator()){ + $OutputScopes += [PSCustomObject]@{ + 'Function' = $FunctionName + 'Service' = $ServiceKey + 'Scope' = $ScopeString + } + } + } + # If no items were found, create an empty record. + If ($Script:FunctionScopes[$FunctionName].Count -eq 0){ + $OutputScopes += [PSCustomObject]@{ + 'Function' = $FunctionName + 'Service' = $null + 'Scope' = $null + } + } + } +} + +# Generate datasets that will be used to validate function parameters +$ValidServices = $OutputScopes | Select-Object -ExpandProperty 'Service' -Unique +$ValidFunctions = $OutputScopes | Select-Object -ExpandProperty 'Function' -Unique +$ValidScopes = $OutputScopes | Select-Object -ExpandProperty 'Scope' -Unique + +# Return the output +$HashOutput = @{} + + +# \Module\OAuthScopes.ps1 +$Code = @" +# Scope data that is used by the Get-PSGSuiteOAuthScope function. +`$script:_PSGSuiteOAuthScopes = @' +$($OutputScopes | ConvertTo-Json) +'@ | ConvertFrom-Json +"@ +$HashOutput['\Module\OAuthScopes.ps1'] = $Code + + +# \Class\PSGSuiteValidServiceValues.ps1 +$Code = @" +# Class that provides parameter validation for the Google API services that are used by PSGSuite. +class PSGSuiteValidServiceValues : System.Management.Automation.IValidateSetValuesGenerator { + [string[]] GetValidValues() { + `$Values = @( + '$($ValidServices -join "',`n '")' + ) + return `$Values + } +} +"@ +$HashOutput['\Class\PSGSuiteValidServiceValues.ps1'] = $Code + + +# \Class\PSGSuiteValidFunctionValues.ps1 +$Code = @" +# Class that provides parameter validation for the names of the public PSGSuite functions. +class PSGSuiteValidFunctionValues : System.Management.Automation.IValidateSetValuesGenerator { + [string[]] GetValidValues() { + `$Values = @( + '$($ValidFunctions -join "',`n '")' + ) + return `$Values + } +} +"@ +$HashOutput['\Class\PSGSuiteValidFunctionValues.ps1'] = $Code + + +# \Class\PSGSuiteValidOAuthScopeValues +$Code = @" +# Class that provides parameter validation for the list of OAuth scopes that are used by all PSGSuite functions. +class PSGSuiteValidOAuthScopeValues : System.Management.Automation.IValidateSetValuesGenerator { + [string[]] GetValidValues() { + `$Values = @( + '$($ValidScopes -join "',`n '")' + ) + return `$Values + } +} +"@ +$HashOutput['\Class\PSGSuiteValidOAuthScopeValues.ps1'] = $Code + + +$HashOutput From 1dcd968cf7512df2d2f9d9be8c86986526568429 Mon Sep 17 00:00:00 2001 From: Foggy2 <23325075+Foggy2@users.noreply.github.com> Date: Mon, 23 Jun 2025 16:34:25 +0930 Subject: [PATCH 10/13] Added class `PSGSuiteValidClientSecretOAuthScopeValues` and sorted class values for easier change tracking --- ...SuiteValidClientSecretOAuthScopeValues.ps1 | 269 ++++ .../Class/PSGSuiteValidFunctionValues.ps1 | 394 ++--- .../Class/PSGSuiteValidOAuthScopeValues.ps1 | 38 +- PSGSuite/Class/PSGSuiteValidServiceValues.ps1 | 14 +- PSGSuite/Module/OAuthScopes.ps1 | 1398 ++++++++--------- ci/templates/OAuthScopes.ps1 | 26 +- 6 files changed, 1214 insertions(+), 925 deletions(-) create mode 100644 PSGSuite/Class/PSGSuiteValidClientSecretOAuthScopeValues.ps1 diff --git a/PSGSuite/Class/PSGSuiteValidClientSecretOAuthScopeValues.ps1 b/PSGSuite/Class/PSGSuiteValidClientSecretOAuthScopeValues.ps1 new file mode 100644 index 00000000..8ae15552 --- /dev/null +++ b/PSGSuite/Class/PSGSuiteValidClientSecretOAuthScopeValues.ps1 @@ -0,0 +1,269 @@ +# Programmatically generated from template 'OAuthScopes.ps1' +# This file will be overwritten during the module build process. + +class PSGSuiteValidClientSecretOAuthScopeValues : System.Management.Automation.IValidateSetValuesGenerator { + [string[]] GetValidValues() { + $Values = @( + 'Google.Apis.Admin.DataTransfer.datatransfer_v1.DataTransferService', + 'Google.Apis.Admin.Directory.directory_v1.DirectoryService', + 'Google.Apis.Admin.Reports.reports_v1.ReportsService', + 'Google.Apis.Calendar.v3.CalendarService', + 'Google.Apis.Classroom.v1.ClassroomService', + 'Google.Apis.Drive.v3.DriveService', + 'Google.Apis.Gmail.v1.GmailService', + 'Google.Apis.Groupssettings.v1.GroupssettingsService', + 'Google.Apis.HangoutsChat.v1.HangoutsChatService', + 'Google.Apis.Licensing.v1.LicensingService', + 'Google.Apis.Sheets.v4.SheetsService', + 'Google.Apis.Slides.v1.SlidesService', + 'Add-GSCalendarEventReminder', + 'Add-GSCalendarNotification', + 'Add-GSCalendarSubscription', + 'Add-GSChatButton', + 'Add-GSChatCard', + 'Add-GSChatCardAction', + 'Add-GSChatCardSection', + 'Add-GSChatImage', + 'Add-GSChatKeyValue', + 'Add-GSChatOnClick', + 'Add-GSChatTextParagraph', + 'Add-GSCourseParticipant', + 'Add-GSCustomerPostalAddress', + 'Add-GSDocContent', + 'Add-GSDrivePermission', + 'Add-GSEventAttendee', + 'Add-GSGmailDelegate', + 'Add-GSGmailFilter', + 'Add-GSGmailForwardingAddress', + 'Add-GSGmailSmtpMsa', + 'Add-GSGroupMember', + 'Add-GSPrincipalGroupMembership', + 'Add-GSSheetValues', + 'Add-GSUserAddress', + 'Add-GSUserEmail', + 'Add-GSUserExternalId', + 'Add-GSUserIm', + 'Add-GSUserLocation', + 'Add-GSUserOrganization', + 'Add-GSUserPhone', + 'Add-GSUserRelation', + 'Add-GSUserSchemaField', + 'Block-CoreCLREncryptionWarning', + 'Clear-GSSheet', + 'Clear-PSGSuiteServiceCache', + 'Compare-ModuleVersion', + 'Confirm-GSCourseInvitation', + 'Copy-GSDriveFile', + 'Copy-GSSheet', + 'Edit-GSPresentation', + 'Export-GSDriveFile', + 'Export-GSSheet', + 'Export-PSGSuiteConfig', + 'Get-GSActivityReport', + 'Get-GSAdminRole', + 'Get-GSAdminRoleAssignment', + 'Get-GSCalendar', + 'Get-GSCalendarACL', + 'Get-GSCalendarEvent', + 'Get-GSCalendarSubscription', + 'Get-GSChatConfig', + 'Get-GSChatMember', + 'Get-GSChatMessage', + 'Get-GSChatSpace', + 'Get-GSChromeOSDevice', + 'Get-GSClassroomUserProfile', + 'Get-GSCourse', + 'Get-GSCourseAlias', + 'Get-GSCourseInvitation', + 'Get-GSCourseParticipant', + 'Get-GSCustomer', + 'Get-GSDataTransfer', + 'Get-GSDataTransferApplication', + 'Get-GSDocContent', + 'Get-GSDomain', + 'Get-GSDomainAlias', + 'Get-GSDrive', + 'Get-GSDriveFile', + 'Get-GSDriveFileList', + 'Get-GSDriveFileUploadStatus', + 'Get-GSDriveFolderSize', + 'Get-GSDrivePermission', + 'Get-GSDriveProfile', + 'Get-GSDriveRevision', + 'Get-GSGmailAutoForwardingSettings', + 'Get-GSGmailDelegate', + 'Get-GSGmailFilter', + 'Get-GSGmailForwardingAddress', + 'Get-GSGmailImapSettings', + 'Get-GSGmailLanguageSettings', + 'Get-GSGmailPopSettings', + 'Get-GSGmailSendAsAlias', + 'Get-GSGmailSMIMEInfo', + 'Get-GSGmailVacationSettings', + 'Get-GSGroup', + 'Get-GSGroupAlias', + 'Get-GSGroupMember', + 'Get-GSGroupSettings', + 'Get-GSMobileDevice', + 'Get-GSOrganizationalUnit', + 'Get-GSPresentation', + 'Get-GSResource', + 'Get-GSSheetInfo', + 'Get-GSStudentGuardian', + 'Get-GSStudentGuardianInvitation', + 'Get-GSUsageReport', + 'Get-GSUser', + 'Get-GSUserAlias', + 'Get-GSUserASP', + 'Get-GSUserLicense', + 'Get-GSUserPhoto', + 'Get-GSUserSchema', + 'Get-GSUserToken', + 'Get-GSUserVerificationCodes', + 'Get-PSGSuiteConfig', + 'Get-PSGSuiteOAuthScope', + 'Get-PSGSuiteServiceCache', + 'Hide-GSDrive', + 'Import-GSSheet', + 'Import-PSGSuiteConfig', + 'Invoke-GSUserOffboarding', + 'New-GoogleService', + 'New-GSAdminRole', + 'New-GSAdminRoleAssignment', + 'New-GSCalendarACL', + 'New-GSCalendarEvent', + 'New-GSCourse', + 'New-GSCourseAlias', + 'New-GSCourseInvitation', + 'New-GSDomain', + 'New-GSDomainAlias', + 'New-GSDrive', + 'New-GSDriveFile', + 'New-GSGmailSendAsAlias', + 'New-GSGmailSMIMEInfo', + 'New-GSGroup', + 'New-GSGroupAlias', + 'New-GSOrganizationalUnit', + 'New-GSPresentationUpdateRequest', + 'New-GSResource', + 'New-GSSheet', + 'New-GSStudentGuardianInvitation', + 'New-GSUser', + 'New-GSUserAlias', + 'New-GSUserSchema', + 'New-GSUserVerificationCodes', + 'Remove-GSAdminRole', + 'Remove-GSAdminRoleAssignment', + 'Remove-GSCalendarAcl', + 'Remove-GSCalendarEvent', + 'Remove-GSCalendarSubscription', + 'Remove-GSChatMessage', + 'Remove-GSCourse', + 'Remove-GSCourseAlias', + 'Remove-GSCourseInvitation', + 'Remove-GSCourseParticipant', + 'Remove-GSDomain', + 'Remove-GSDomainAlias', + 'Remove-GSDrive', + 'Remove-GSDriveFile', + 'Remove-GSDrivePermission', + 'Remove-GSDriveRevision', + 'Remove-GSGmailDelegate', + 'Remove-GSGmailFilter', + 'Remove-GSGmailSendAsAlias', + 'Remove-GSGmailSMIMEInfo', + 'Remove-GSGroup', + 'Remove-GSGroupAlias', + 'Remove-GSGroupMember', + 'Remove-GSMobileDevice', + 'Remove-GSOrganizationalUnit', + 'Remove-GSPrincipalGroupMembership', + 'Remove-GSResource', + 'Remove-GSStudentGuardian', + 'Remove-GSUser', + 'Remove-GSUserAlias', + 'Remove-GSUserASP', + 'Remove-GSUserLicense', + 'Remove-GSUserPhoto', + 'Remove-GSUserSchema', + 'Remove-GSUserToken', + 'Restore-GSUser', + 'Revoke-GSStudentGuardianInvitation', + 'Revoke-GSUserVerificationCodes', + 'Send-GSChatMessage', + 'Send-GSGmailSendAsConfirmation', + 'Set-GSDocContent', + 'Set-GSGroupSettings', + 'Set-GSUserLicense', + 'Set-GSUserSchema', + 'Set-PSGSuiteConfig', + 'Show-GSDrive', + 'Show-PSGSuiteConfig', + 'Start-GSDataTransfer', + 'Start-GSDriveFileUpload', + 'Stop-GSDriveFileUpload', + 'Switch-PSGSuiteConfig', + 'Sync-GSUserCache', + 'Test-GSGroupMembership', + 'Unblock-CoreCLREncryptionWarning', + 'Update-GSAdminRole', + 'Update-GSCalendarEvent', + 'Update-GSCalendarSubscription', + 'Update-GSChatMessage', + 'Update-GSChromeOSDevice', + 'Update-GSCourse', + 'Update-GSCustomer', + 'Update-GSDrive', + 'Update-GSDriveFile', + 'Update-GSDriveRevision', + 'Update-GSGmailAutoForwardingSettings', + 'Update-GSGmailImapSettings', + 'Update-GSGmailLanguageSettings', + 'Update-GSGmailPopSettings', + 'Update-GSGmailSendAsAlias', + 'Update-GSGmailSignature', + 'Update-GSGmailVacationSettings', + 'Update-GSGroup', + 'Update-GSGroupMember', + 'Update-GSGroupSettings', + 'Update-GSMobileDevice', + 'Update-GSOrganizationalUnit', + 'Update-GSResource', + 'Update-GSSheet', + 'Update-GSUser', + 'Update-GSUserLicense', + 'Update-GSUserPhoto', + 'Update-GSUserSchema', + 'Watch-GSDriveUpload', + 'https://www.googleapis.com/auth/admin.datatransfer', + 'https://www.googleapis.com/auth/admin.directory.customer', + 'https://www.googleapis.com/auth/admin.directory.device.chromeos', + 'https://www.googleapis.com/auth/admin.directory.device.mobile', + 'https://www.googleapis.com/auth/admin.directory.domain', + 'https://www.googleapis.com/auth/admin.directory.group', + 'https://www.googleapis.com/auth/admin.directory.orgunit', + 'https://www.googleapis.com/auth/admin.directory.resource.calendar', + 'https://www.googleapis.com/auth/admin.directory.rolemanagement', + 'https://www.googleapis.com/auth/admin.directory.rolemanagement.readonly', + 'https://www.googleapis.com/auth/admin.directory.user', + 'https://www.googleapis.com/auth/admin.directory.user.readonly', + 'https://www.googleapis.com/auth/admin.directory.user.security', + 'https://www.googleapis.com/auth/admin.directory.userschema', + 'https://www.googleapis.com/auth/admin.reports.audit.readonly', + 'https://www.googleapis.com/auth/admin.reports.usage.readonly', + 'https://www.googleapis.com/auth/apps.groups.settings', + 'https://www.googleapis.com/auth/apps.licensing', + 'https://www.googleapis.com/auth/calendar', + 'https://www.googleapis.com/auth/chat.bot', + 'https://www.googleapis.com/auth/classroom.courses', + 'https://www.googleapis.com/auth/classroom.guardianlinks.students', + 'https://www.googleapis.com/auth/classroom.profile.emails', + 'https://www.googleapis.com/auth/classroom.profile.photos', + 'https://www.googleapis.com/auth/classroom.rosters', + 'https://www.googleapis.com/auth/drive', + 'https://www.googleapis.com/auth/gmail.settings.basic', + 'https://www.googleapis.com/auth/gmail.settings.sharing' + ) + return $Values + } +} diff --git a/PSGSuite/Class/PSGSuiteValidFunctionValues.ps1 b/PSGSuite/Class/PSGSuiteValidFunctionValues.ps1 index 214c02b3..10a5f252 100644 --- a/PSGSuite/Class/PSGSuiteValidFunctionValues.ps1 +++ b/PSGSuite/Class/PSGSuiteValidFunctionValues.ps1 @@ -5,225 +5,225 @@ class PSGSuiteValidFunctionValues : System.Management.Automation.IValidateSetValuesGenerator { [string[]] GetValidValues() { $Values = @( - 'Remove-GSStudentGuardian', - 'Remove-GSUserLicense', - 'New-GSGroup', - 'New-GSDomainAlias', - 'Update-GSGmailImapSettings', - 'Get-GSCalendarACL', - 'Unblock-CoreCLREncryptionWarning', - 'Start-GSDriveFileUpload', - 'Show-PSGSuiteConfig', - 'Remove-GSDrivePermission', - 'Remove-GSGroup', - 'Update-GSResource', - 'Set-GSDocContent', - 'Update-GSDriveRevision', - 'Remove-GSGroupAlias', - 'Clear-GSSheet', - 'Remove-GSAdminRole', - 'New-GSDriveFile', - 'Get-GSGroupAlias', - 'Get-GSUserLicense', - 'Update-GSSheet', - 'Get-GSUserToken', - 'Get-GSGmailVacationSettings', - 'Remove-GSDriveRevision', - 'Update-GSUserPhoto', - 'Remove-GSUserPhoto', - 'Remove-GSGmailDelegate', - 'Get-GSDriveFile', - 'Update-GSCalendarEvent', - 'New-GSCourse', - 'Remove-GSCalendarEvent', - 'Remove-GSDriveFile', 'Add-GSCalendarEventReminder', - 'Remove-GSUserAlias', + 'Add-GSCalendarNotification', + 'Add-GSCalendarSubscription', + 'Add-GSChatButton', + 'Add-GSChatCard', + 'Add-GSChatCardAction', + 'Add-GSChatCardSection', + 'Add-GSChatImage', + 'Add-GSChatKeyValue', + 'Add-GSChatOnClick', + 'Add-GSChatTextParagraph', + 'Add-GSCourseParticipant', + 'Add-GSCustomerPostalAddress', + 'Add-GSDocContent', + 'Add-GSDrivePermission', + 'Add-GSEventAttendee', + 'Add-GSGmailDelegate', + 'Add-GSGmailFilter', + 'Add-GSGmailForwardingAddress', + 'Add-GSGmailSmtpMsa', + 'Add-GSGroupMember', 'Add-GSPrincipalGroupMembership', - 'Remove-GSCalendarAcl', - 'Update-GSGmailPopSettings', - 'Get-GSCourseInvitation', - 'Remove-GSUserASP', - 'Remove-GSAdminRoleAssignment', + 'Add-GSSheetValues', 'Add-GSUserAddress', - 'Get-GSChatSpace', + 'Add-GSUserEmail', + 'Add-GSUserExternalId', + 'Add-GSUserIm', + 'Add-GSUserLocation', + 'Add-GSUserOrganization', + 'Add-GSUserPhone', + 'Add-GSUserRelation', + 'Add-GSUserSchemaField', 'Block-CoreCLREncryptionWarning', - 'New-GSGroupAlias', - 'Send-GSChatMessage', - 'Update-GSMobileDevice', - 'Update-GSGroupMember', + 'Clear-GSSheet', + 'Clear-PSGSuiteServiceCache', + 'Compare-ModuleVersion', + 'Confirm-GSCourseInvitation', + 'Copy-GSDriveFile', + 'Copy-GSSheet', + 'Edit-GSPresentation', 'Export-GSDriveFile', - 'Get-GSDriveRevision', - 'Get-GSDriveFileUploadStatus', - 'Watch-GSDriveUpload', + 'Export-GSSheet', + 'Export-PSGSuiteConfig', + 'Get-GSActivityReport', + 'Get-GSAdminRole', + 'Get-GSAdminRoleAssignment', + 'Get-GSCalendar', + 'Get-GSCalendarACL', + 'Get-GSCalendarEvent', + 'Get-GSCalendarSubscription', + 'Get-GSChatConfig', + 'Get-GSChatMember', 'Get-GSChatMessage', - 'Get-GSOrganizationalUnit', - 'New-GSPresentationUpdateRequest', - 'Import-GSSheet', - 'New-GSGmailSendAsAlias', - 'Update-GSDrive', - 'Remove-GSOrganizationalUnit', - 'Copy-GSDriveFile', - 'Remove-GSGmailSendAsAlias', - 'Restore-GSUser', - 'New-GSGmailSMIMEInfo', - 'Update-GSGroup', + 'Get-GSChatSpace', + 'Get-GSChromeOSDevice', + 'Get-GSClassroomUserProfile', + 'Get-GSCourse', + 'Get-GSCourseAlias', + 'Get-GSCourseInvitation', + 'Get-GSCourseParticipant', + 'Get-GSCustomer', + 'Get-GSDataTransfer', + 'Get-GSDataTransferApplication', + 'Get-GSDocContent', + 'Get-GSDomain', + 'Get-GSDomainAlias', + 'Get-GSDrive', + 'Get-GSDriveFile', + 'Get-GSDriveFileList', + 'Get-GSDriveFileUploadStatus', + 'Get-GSDriveFolderSize', + 'Get-GSDrivePermission', + 'Get-GSDriveProfile', + 'Get-GSDriveRevision', 'Get-GSGmailAutoForwardingSettings', - 'Add-GSChatTextParagraph', - 'Get-GSUserAlias', - 'Remove-GSChatMessage', - 'Add-GSChatOnClick', - 'Update-GSGmailVacationSettings', - 'Remove-GSGmailFilter', - 'Add-GSChatKeyValue', - 'Get-GSUsageReport', - 'Get-GSUserSchema', + 'Get-GSGmailDelegate', + 'Get-GSGmailFilter', + 'Get-GSGmailForwardingAddress', + 'Get-GSGmailImapSettings', + 'Get-GSGmailLanguageSettings', 'Get-GSGmailPopSettings', - 'New-GSDrive', - 'Add-GSCustomerPostalAddress', - 'Update-GSUserSchema', - 'Get-GSDomain', + 'Get-GSGmailSendAsAlias', + 'Get-GSGmailSMIMEInfo', + 'Get-GSGmailVacationSettings', + 'Get-GSGroup', + 'Get-GSGroupAlias', + 'Get-GSGroupMember', 'Get-GSGroupSettings', - 'Get-GSGmailFilter', + 'Get-GSMobileDevice', + 'Get-GSOrganizationalUnit', + 'Get-GSPresentation', + 'Get-GSResource', + 'Get-GSSheetInfo', 'Get-GSStudentGuardian', - 'Update-GSGmailSendAsAlias', - 'Add-GSDocContent', - 'Remove-GSUserToken', - 'Get-GSCourse', - 'Clear-PSGSuiteServiceCache', - 'Get-GSGmailSendAsAlias', - 'Set-PSGSuiteConfig', - 'Add-GSCalendarSubscription', + 'Get-GSStudentGuardianInvitation', + 'Get-GSUsageReport', + 'Get-GSUser', + 'Get-GSUserAlias', + 'Get-GSUserASP', + 'Get-GSUserLicense', + 'Get-GSUserPhoto', + 'Get-GSUserSchema', + 'Get-GSUserToken', + 'Get-GSUserVerificationCodes', + 'Get-PSGSuiteConfig', + 'Get-PSGSuiteOAuthScope', + 'Get-PSGSuiteServiceCache', + 'Hide-GSDrive', + 'Import-GSSheet', 'Import-PSGSuiteConfig', - 'New-GSUserSchema', - 'Get-GSDriveFolderSize', 'Invoke-GSUserOffboarding', - 'Remove-GSDrive', - 'Add-GSChatCard', - 'Update-GSCourse', + 'New-GoogleService', + 'New-GSAdminRole', + 'New-GSAdminRoleAssignment', + 'New-GSCalendarACL', + 'New-GSCalendarEvent', + 'New-GSCourse', + 'New-GSCourseAlias', + 'New-GSCourseInvitation', + 'New-GSDomain', + 'New-GSDomainAlias', + 'New-GSDrive', + 'New-GSDriveFile', + 'New-GSGmailSendAsAlias', + 'New-GSGmailSMIMEInfo', + 'New-GSGroup', + 'New-GSGroupAlias', + 'New-GSOrganizationalUnit', + 'New-GSPresentationUpdateRequest', + 'New-GSResource', + 'New-GSSheet', + 'New-GSStudentGuardianInvitation', + 'New-GSUser', + 'New-GSUserAlias', + 'New-GSUserSchema', + 'New-GSUserVerificationCodes', + 'Remove-GSAdminRole', + 'Remove-GSAdminRoleAssignment', + 'Remove-GSCalendarAcl', + 'Remove-GSCalendarEvent', + 'Remove-GSCalendarSubscription', + 'Remove-GSChatMessage', + 'Remove-GSCourse', + 'Remove-GSCourseAlias', + 'Remove-GSCourseInvitation', 'Remove-GSCourseParticipant', - 'Get-GSDrivePermission', - 'Get-GSActivityReport', - 'Set-GSUserSchema', - 'Get-GSGmailLanguageSettings', + 'Remove-GSDomain', + 'Remove-GSDomainAlias', + 'Remove-GSDrive', + 'Remove-GSDriveFile', + 'Remove-GSDrivePermission', + 'Remove-GSDriveRevision', + 'Remove-GSGmailDelegate', + 'Remove-GSGmailFilter', + 'Remove-GSGmailSendAsAlias', + 'Remove-GSGmailSMIMEInfo', + 'Remove-GSGroup', + 'Remove-GSGroupAlias', + 'Remove-GSGroupMember', + 'Remove-GSMobileDevice', + 'Remove-GSOrganizationalUnit', + 'Remove-GSPrincipalGroupMembership', + 'Remove-GSResource', + 'Remove-GSStudentGuardian', + 'Remove-GSUser', + 'Remove-GSUserAlias', + 'Remove-GSUserASP', + 'Remove-GSUserLicense', + 'Remove-GSUserPhoto', + 'Remove-GSUserSchema', + 'Remove-GSUserToken', + 'Restore-GSUser', 'Revoke-GSStudentGuardianInvitation', - 'Get-GSUserASP', - 'Get-GSDrive', - 'Add-GSUserPhone', - 'Set-GSUserLicense', - 'Get-GSResource', - 'New-GSUserVerificationCodes', + 'Revoke-GSUserVerificationCodes', + 'Send-GSChatMessage', + 'Send-GSGmailSendAsConfirmation', + 'Set-GSDocContent', 'Set-GSGroupSettings', - 'New-GSDomain', - 'Get-PSGSuiteConfig', - 'New-GSCalendarACL', - 'Add-GSCourseParticipant', - 'Get-GSMobileDevice', - 'Remove-GSGroupMember', - 'Get-GSStudentGuardianInvitation', - 'Get-GSSheetInfo', - 'Get-GSDocContent', + 'Set-GSUserLicense', + 'Set-GSUserSchema', + 'Set-PSGSuiteConfig', + 'Show-GSDrive', + 'Show-PSGSuiteConfig', 'Start-GSDataTransfer', - 'Update-GSUser', - 'Remove-GSUserSchema', - 'Add-GSGmailForwardingAddress', - 'Get-GSDriveFileList', - 'Add-GSGmailSmtpMsa', - 'Get-GSGmailSMIMEInfo', - 'Add-GSGmailFilter', - 'Update-GSGmailSignature', - 'Update-GSOrganizationalUnit', - 'Compare-ModuleVersion', - 'New-GSAdminRole', - 'Confirm-GSCourseInvitation', - 'Get-GSCalendarEvent', - 'Remove-GSCourse', - 'Update-GSCalendarSubscription', + 'Start-GSDriveFileUpload', + 'Stop-GSDriveFileUpload', + 'Switch-PSGSuiteConfig', 'Sync-GSUserCache', - 'Add-GSUserOrganization', - 'Update-GSUserLicense', + 'Test-GSGroupMembership', + 'Unblock-CoreCLREncryptionWarning', + 'Update-GSAdminRole', + 'Update-GSCalendarEvent', + 'Update-GSCalendarSubscription', 'Update-GSChatMessage', - 'Get-GSDataTransfer', - 'New-GSCourseInvitation', - 'Revoke-GSUserVerificationCodes', - 'Stop-GSDriveFileUpload', - 'Remove-GSGmailSMIMEInfo', - 'Edit-GSPresentation', - 'Add-GSChatButton', - 'Get-GSClassroomUserProfile', - 'Get-GSGmailDelegate', - 'Remove-GSCourseAlias', - 'Remove-GSCourseInvitation', - 'Get-GSCalendarSubscription', - 'Get-GSCourseParticipant', - 'New-GSUserAlias', - 'Show-GSDrive', - 'Copy-GSSheet', - 'Get-GSPresentation', 'Update-GSChromeOSDevice', - 'Remove-GSCalendarSubscription', - 'Remove-GSUser', - 'New-GSOrganizationalUnit', - 'Add-GSUserLocation', - 'Add-GSGmailDelegate', - 'Remove-GSDomainAlias', + 'Update-GSCourse', + 'Update-GSCustomer', + 'Update-GSDrive', 'Update-GSDriveFile', - 'Add-GSChatCardSection', - 'Add-GSCalendarNotification', - 'Update-GSGmailLanguageSettings', - 'Add-GSUserSchemaField', - 'Get-GSGmailForwardingAddress', - 'Add-GSSheetValues', - 'Get-PSGSuiteServiceCache', - 'New-GSStudentGuardianInvitation', - 'New-GSCourseAlias', - 'Add-GSChatCardAction', - 'Remove-GSDomain', - 'Get-GSDriveProfile', - 'Hide-GSDrive', - 'New-GSSheet', - 'New-GSCalendarEvent', - 'Get-PSGSuiteOAuthScope', - 'Get-GSUserPhoto', - 'Switch-PSGSuiteConfig', + 'Update-GSDriveRevision', 'Update-GSGmailAutoForwardingSettings', - 'Export-GSSheet', - 'Get-GSUser', - 'Add-GSDrivePermission', - 'New-GSResource', - 'Get-GSDomainAlias', - 'Export-PSGSuiteConfig', - 'Remove-GSResource', - 'Get-GSGroup', - 'Get-GSChromeOSDevice', - 'Update-GSCustomer', - 'Update-GSAdminRole', - 'New-GSAdminRoleAssignment', - 'Get-GSCourseAlias', - 'Get-GSGroupMember', - 'Get-GSChatConfig', - 'Add-GSUserExternalId', - 'Get-GSCalendar', - 'Add-GSGroupMember', - 'Get-GSUserVerificationCodes', - 'New-GSUser', - 'Test-GSGroupMembership', - 'Add-GSEventAttendee', - 'Remove-GSPrincipalGroupMembership', - 'Get-GSAdminRoleAssignment', - 'Add-GSUserEmail', - 'Add-GSUserIm', + 'Update-GSGmailImapSettings', + 'Update-GSGmailLanguageSettings', + 'Update-GSGmailPopSettings', + 'Update-GSGmailSendAsAlias', + 'Update-GSGmailSignature', + 'Update-GSGmailVacationSettings', + 'Update-GSGroup', + 'Update-GSGroupMember', 'Update-GSGroupSettings', - 'Get-GSGmailImapSettings', - 'Get-GSAdminRole', - 'Add-GSChatImage', - 'Add-GSUserRelation', - 'Get-GSChatMember', - 'Send-GSGmailSendAsConfirmation', - 'New-GoogleService', - 'Remove-GSMobileDevice', - 'Get-GSDataTransferApplication', - 'Get-GSCustomer' + 'Update-GSMobileDevice', + 'Update-GSOrganizationalUnit', + 'Update-GSResource', + 'Update-GSSheet', + 'Update-GSUser', + 'Update-GSUserLicense', + 'Update-GSUserPhoto', + 'Update-GSUserSchema', + 'Watch-GSDriveUpload' ) return $Values } diff --git a/PSGSuite/Class/PSGSuiteValidOAuthScopeValues.ps1 b/PSGSuite/Class/PSGSuiteValidOAuthScopeValues.ps1 index 8069a320..aff5c7d0 100644 --- a/PSGSuite/Class/PSGSuiteValidOAuthScopeValues.ps1 +++ b/PSGSuite/Class/PSGSuiteValidOAuthScopeValues.ps1 @@ -5,34 +5,34 @@ class PSGSuiteValidOAuthScopeValues : System.Management.Automation.IValidateSetValuesGenerator { [string[]] GetValidValues() { $Values = @( - 'https://www.googleapis.com/auth/classroom.guardianlinks.students', - 'https://www.googleapis.com/auth/apps.licensing', - 'https://www.googleapis.com/auth/admin.directory.group', + 'https://www.googleapis.com/auth/admin.datatransfer', + 'https://www.googleapis.com/auth/admin.directory.customer', + 'https://www.googleapis.com/auth/admin.directory.device.chromeos', + 'https://www.googleapis.com/auth/admin.directory.device.mobile', 'https://www.googleapis.com/auth/admin.directory.domain', - 'https://www.googleapis.com/auth/gmail.settings.basic', - 'https://www.googleapis.com/auth/calendar', - 'https://www.googleapis.com/auth/drive', + 'https://www.googleapis.com/auth/admin.directory.group', + 'https://www.googleapis.com/auth/admin.directory.orgunit', 'https://www.googleapis.com/auth/admin.directory.resource.calendar', 'https://www.googleapis.com/auth/admin.directory.rolemanagement', + 'https://www.googleapis.com/auth/admin.directory.rolemanagement.readonly', 'https://www.googleapis.com/auth/admin.directory.user', - 'https://www.googleapis.com/auth/admin.directory.user.security', - 'https://www.googleapis.com/auth/gmail.settings.sharing', - 'https://www.googleapis.com/auth/classroom.courses', - 'https://www.googleapis.com/auth/classroom.rosters', - 'https://www.googleapis.com/auth/chat.bot', 'https://www.googleapis.com/auth/admin.directory.user.readonly', - 'https://www.googleapis.com/auth/admin.directory.device.mobile', - 'https://www.googleapis.com/auth/admin.directory.orgunit', - 'https://www.googleapis.com/auth/admin.reports.usage.readonly', + 'https://www.googleapis.com/auth/admin.directory.user.security', 'https://www.googleapis.com/auth/admin.directory.userschema', - 'https://www.googleapis.com/auth/apps.groups.settings', 'https://www.googleapis.com/auth/admin.reports.audit.readonly', - 'https://www.googleapis.com/auth/admin.datatransfer', + 'https://www.googleapis.com/auth/admin.reports.usage.readonly', + 'https://www.googleapis.com/auth/apps.groups.settings', + 'https://www.googleapis.com/auth/apps.licensing', + 'https://www.googleapis.com/auth/calendar', + 'https://www.googleapis.com/auth/chat.bot', + 'https://www.googleapis.com/auth/classroom.courses', + 'https://www.googleapis.com/auth/classroom.guardianlinks.students', 'https://www.googleapis.com/auth/classroom.profile.emails', 'https://www.googleapis.com/auth/classroom.profile.photos', - 'https://www.googleapis.com/auth/admin.directory.device.chromeos', - 'https://www.googleapis.com/auth/admin.directory.customer', - 'https://www.googleapis.com/auth/admin.directory.rolemanagement.readonly' + 'https://www.googleapis.com/auth/classroom.rosters', + 'https://www.googleapis.com/auth/drive', + 'https://www.googleapis.com/auth/gmail.settings.basic', + 'https://www.googleapis.com/auth/gmail.settings.sharing' ) return $Values } diff --git a/PSGSuite/Class/PSGSuiteValidServiceValues.ps1 b/PSGSuite/Class/PSGSuiteValidServiceValues.ps1 index b5b55002..14f5c289 100644 --- a/PSGSuite/Class/PSGSuiteValidServiceValues.ps1 +++ b/PSGSuite/Class/PSGSuiteValidServiceValues.ps1 @@ -5,17 +5,17 @@ class PSGSuiteValidServiceValues : System.Management.Automation.IValidateSetValuesGenerator { [string[]] GetValidValues() { $Values = @( - 'Google.Apis.Classroom.v1.ClassroomService', - 'Google.Apis.Licensing.v1.LicensingService', + 'Google.Apis.Admin.DataTransfer.datatransfer_v1.DataTransferService', 'Google.Apis.Admin.Directory.directory_v1.DirectoryService', - 'Google.Apis.Gmail.v1.GmailService', + 'Google.Apis.Admin.Reports.reports_v1.ReportsService', 'Google.Apis.Calendar.v3.CalendarService', + 'Google.Apis.Classroom.v1.ClassroomService', 'Google.Apis.Drive.v3.DriveService', - 'Google.Apis.Sheets.v4.SheetsService', - 'Google.Apis.HangoutsChat.v1.HangoutsChatService', - 'Google.Apis.Admin.Reports.reports_v1.ReportsService', + 'Google.Apis.Gmail.v1.GmailService', 'Google.Apis.Groupssettings.v1.GroupssettingsService', - 'Google.Apis.Admin.DataTransfer.datatransfer_v1.DataTransferService', + 'Google.Apis.HangoutsChat.v1.HangoutsChatService', + 'Google.Apis.Licensing.v1.LicensingService', + 'Google.Apis.Sheets.v4.SheetsService', 'Google.Apis.Slides.v1.SlidesService' ) return $Values diff --git a/PSGSuite/Module/OAuthScopes.ps1 b/PSGSuite/Module/OAuthScopes.ps1 index 65d9b69c..a92e0566 100644 --- a/PSGSuite/Module/OAuthScopes.ps1 +++ b/PSGSuite/Module/OAuthScopes.ps1 @@ -5,709 +5,699 @@ $script:_PSGSuiteOAuthScopes = @' [ { - "Function": "Remove-GSStudentGuardian", - "Service": "Google.Apis.Classroom.v1.ClassroomService", - "Scope": "https://www.googleapis.com/auth/classroom.guardianlinks.students" + "Function": "Add-GSCalendarEventReminder", + "Service": null, + "Scope": null }, { - "Function": "Remove-GSUserLicense", - "Service": "Google.Apis.Licensing.v1.LicensingService", - "Scope": "https://www.googleapis.com/auth/apps.licensing" + "Function": "Add-GSCalendarNotification", + "Service": null, + "Scope": null }, { - "Function": "New-GSGroup", - "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.group" + "Function": "Add-GSChatButton", + "Service": null, + "Scope": null }, { - "Function": "New-GSDomainAlias", - "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.domain" + "Function": "Add-GSChatCard", + "Service": null, + "Scope": null }, { - "Function": "Update-GSGmailImapSettings", - "Service": "Google.Apis.Gmail.v1.GmailService", - "Scope": "https://www.googleapis.com/auth/gmail.settings.basic" + "Function": "Add-GSChatCardAction", + "Service": null, + "Scope": null }, { - "Function": "Get-GSCalendarACL", - "Service": "Google.Apis.Calendar.v3.CalendarService", - "Scope": "https://www.googleapis.com/auth/calendar" + "Function": "Add-GSChatCardSection", + "Service": null, + "Scope": null }, { - "Function": "Unblock-CoreCLREncryptionWarning", + "Function": "Add-GSChatImage", "Service": null, "Scope": null }, { - "Function": "Start-GSDriveFileUpload", - "Service": "Google.Apis.Drive.v3.DriveService", - "Scope": "https://www.googleapis.com/auth/drive" + "Function": "Add-GSChatKeyValue", + "Service": null, + "Scope": null }, { - "Function": "Show-PSGSuiteConfig", + "Function": "Add-GSChatOnClick", "Service": null, "Scope": null }, { - "Function": "Remove-GSDrivePermission", - "Service": "Google.Apis.Drive.v3.DriveService", - "Scope": "https://www.googleapis.com/auth/drive" + "Function": "Add-GSChatTextParagraph", + "Service": null, + "Scope": null }, { - "Function": "Remove-GSGroup", - "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.group" + "Function": "Add-GSCustomerPostalAddress", + "Service": null, + "Scope": null }, { - "Function": "Update-GSResource", - "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.resource.calendar" + "Function": "Add-GSEventAttendee", + "Service": null, + "Scope": null }, { - "Function": "Set-GSDocContent", - "Service": "Google.Apis.Drive.v3.DriveService", - "Scope": "https://www.googleapis.com/auth/drive" + "Function": "Add-GSGmailSmtpMsa", + "Service": null, + "Scope": null }, { - "Function": "Update-GSDriveRevision", - "Service": "Google.Apis.Drive.v3.DriveService", - "Scope": "https://www.googleapis.com/auth/drive" + "Function": "Add-GSUserAddress", + "Service": null, + "Scope": null }, { - "Function": "Remove-GSGroupAlias", - "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.group" + "Function": "Add-GSUserEmail", + "Service": null, + "Scope": null }, { - "Function": "Clear-GSSheet", - "Service": "Google.Apis.Sheets.v4.SheetsService", - "Scope": "https://www.googleapis.com/auth/drive" + "Function": "Add-GSUserExternalId", + "Service": null, + "Scope": null }, { - "Function": "Remove-GSAdminRole", - "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.rolemanagement" + "Function": "Add-GSUserIm", + "Service": null, + "Scope": null }, { - "Function": "New-GSDriveFile", - "Service": "Google.Apis.Drive.v3.DriveService", - "Scope": "https://www.googleapis.com/auth/drive" + "Function": "Add-GSUserLocation", + "Service": null, + "Scope": null }, { - "Function": "Get-GSGroupAlias", - "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.group" + "Function": "Add-GSUserOrganization", + "Service": null, + "Scope": null }, { - "Function": "Get-GSUserLicense", - "Service": "Google.Apis.Licensing.v1.LicensingService", - "Scope": "https://www.googleapis.com/auth/apps.licensing" + "Function": "Add-GSUserPhone", + "Service": null, + "Scope": null }, { - "Function": "Update-GSSheet", - "Service": "Google.Apis.Sheets.v4.SheetsService", - "Scope": "https://www.googleapis.com/auth/drive" + "Function": "Add-GSUserRelation", + "Service": null, + "Scope": null }, { - "Function": "Get-GSUserToken", - "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.user" + "Function": "Add-GSUserSchemaField", + "Service": null, + "Scope": null }, { - "Function": "Get-GSUserToken", - "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.user.security" + "Function": "Block-CoreCLREncryptionWarning", + "Service": null, + "Scope": null }, { - "Function": "Get-GSGmailVacationSettings", - "Service": "Google.Apis.Gmail.v1.GmailService", - "Scope": "https://www.googleapis.com/auth/gmail.settings.basic" + "Function": "Clear-PSGSuiteServiceCache", + "Service": null, + "Scope": null }, { - "Function": "Remove-GSDriveRevision", - "Service": "Google.Apis.Drive.v3.DriveService", - "Scope": "https://www.googleapis.com/auth/drive" + "Function": "Compare-ModuleVersion", + "Service": null, + "Scope": null }, { - "Function": "Update-GSUserPhoto", - "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.user" + "Function": "Export-PSGSuiteConfig", + "Service": null, + "Scope": null }, { - "Function": "Remove-GSUserPhoto", - "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.user" + "Function": "Get-GSChatConfig", + "Service": null, + "Scope": null }, { - "Function": "Remove-GSGmailDelegate", - "Service": "Google.Apis.Gmail.v1.GmailService", - "Scope": "https://www.googleapis.com/auth/gmail.settings.sharing" + "Function": "Get-GSDriveFileUploadStatus", + "Service": null, + "Scope": null }, { - "Function": "Get-GSDriveFile", - "Service": "Google.Apis.Drive.v3.DriveService", - "Scope": "https://www.googleapis.com/auth/drive" + "Function": "Get-PSGSuiteConfig", + "Service": null, + "Scope": null }, { - "Function": "Update-GSCalendarEvent", - "Service": "Google.Apis.Calendar.v3.CalendarService", - "Scope": "https://www.googleapis.com/auth/calendar" + "Function": "Get-PSGSuiteOAuthScope", + "Service": null, + "Scope": null }, { - "Function": "New-GSCourse", - "Service": "Google.Apis.Classroom.v1.ClassroomService", - "Scope": "https://www.googleapis.com/auth/classroom.courses" + "Function": "Get-PSGSuiteServiceCache", + "Service": null, + "Scope": null }, { - "Function": "Remove-GSCalendarEvent", - "Service": "Google.Apis.Calendar.v3.CalendarService", - "Scope": "https://www.googleapis.com/auth/calendar" + "Function": "Import-PSGSuiteConfig", + "Service": null, + "Scope": null }, { - "Function": "Remove-GSDriveFile", - "Service": "Google.Apis.Drive.v3.DriveService", - "Scope": "https://www.googleapis.com/auth/drive" + "Function": "New-GoogleService", + "Service": null, + "Scope": null }, { - "Function": "Add-GSCalendarEventReminder", + "Function": "New-GSPresentationUpdateRequest", "Service": null, "Scope": null }, { - "Function": "Remove-GSUserAlias", - "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.user" + "Function": "Set-PSGSuiteConfig", + "Service": null, + "Scope": null }, { - "Function": "Add-GSPrincipalGroupMembership", - "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.group" + "Function": "Show-PSGSuiteConfig", + "Service": null, + "Scope": null }, { - "Function": "Remove-GSCalendarAcl", - "Service": "Google.Apis.Calendar.v3.CalendarService", - "Scope": "https://www.googleapis.com/auth/calendar" + "Function": "Stop-GSDriveFileUpload", + "Service": null, + "Scope": null }, { - "Function": "Update-GSGmailPopSettings", - "Service": "Google.Apis.Gmail.v1.GmailService", - "Scope": "https://www.googleapis.com/auth/gmail.settings.basic" + "Function": "Switch-PSGSuiteConfig", + "Service": null, + "Scope": null }, { - "Function": "Get-GSCourseInvitation", - "Service": "Google.Apis.Classroom.v1.ClassroomService", - "Scope": "https://www.googleapis.com/auth/classroom.rosters" + "Function": "Unblock-CoreCLREncryptionWarning", + "Service": null, + "Scope": null }, { - "Function": "Remove-GSUserASP", - "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.user" + "Function": "Watch-GSDriveUpload", + "Service": null, + "Scope": null }, { - "Function": "Remove-GSUserASP", - "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.user.security" + "Function": "Get-GSDataTransfer", + "Service": "Google.Apis.Admin.DataTransfer.datatransfer_v1.DataTransferService", + "Scope": "https://www.googleapis.com/auth/admin.datatransfer" }, { - "Function": "Remove-GSAdminRoleAssignment", - "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.rolemanagement" + "Function": "Get-GSDataTransferApplication", + "Service": "Google.Apis.Admin.DataTransfer.datatransfer_v1.DataTransferService", + "Scope": "https://www.googleapis.com/auth/admin.datatransfer" }, { - "Function": "Add-GSUserAddress", - "Service": null, - "Scope": null + "Function": "Start-GSDataTransfer", + "Service": "Google.Apis.Admin.DataTransfer.datatransfer_v1.DataTransferService", + "Scope": "https://www.googleapis.com/auth/admin.datatransfer" }, { - "Function": "Get-GSChatSpace", - "Service": "Google.Apis.HangoutsChat.v1.HangoutsChatService", - "Scope": "https://www.googleapis.com/auth/chat.bot" + "Function": "Add-GSGmailDelegate", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.group" }, { - "Function": "Get-GSChatSpace", + "Function": "Add-GSGroupMember", "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.user" + "Scope": "https://www.googleapis.com/auth/admin.directory.group" }, { - "Function": "Get-GSChatSpace", + "Function": "Add-GSPrincipalGroupMembership", "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.user.readonly" + "Scope": "https://www.googleapis.com/auth/admin.directory.group" }, { - "Function": "Block-CoreCLREncryptionWarning", - "Service": null, - "Scope": null + "Function": "Get-GSAdminRole", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.rolemanagement" }, { - "Function": "New-GSGroupAlias", + "Function": "Get-GSAdminRoleAssignment", "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.group" + "Scope": "https://www.googleapis.com/auth/admin.directory.rolemanagement" }, { - "Function": "Send-GSChatMessage", - "Service": "Google.Apis.HangoutsChat.v1.HangoutsChatService", - "Scope": "https://www.googleapis.com/auth/chat.bot" + "Function": "Get-GSAdminRoleAssignment", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.rolemanagement.readonly" }, { - "Function": "Update-GSMobileDevice", + "Function": "Get-GSAdminRoleAssignment", "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.device.mobile" + "Scope": "https://www.googleapis.com/auth/admin.directory.user" }, { - "Function": "Update-GSGroupMember", + "Function": "Get-GSAdminRoleAssignment", "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.group" + "Scope": "https://www.googleapis.com/auth/admin.directory.user.readonly" }, { - "Function": "Export-GSDriveFile", - "Service": "Google.Apis.Drive.v3.DriveService", - "Scope": "https://www.googleapis.com/auth/drive" + "Function": "Get-GSChatSpace", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.user" }, { - "Function": "Get-GSDriveRevision", - "Service": "Google.Apis.Drive.v3.DriveService", - "Scope": "https://www.googleapis.com/auth/drive" + "Function": "Get-GSChatSpace", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.user.readonly" }, { - "Function": "Get-GSDriveFileUploadStatus", - "Service": null, - "Scope": null + "Function": "Get-GSChromeOSDevice", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.device.chromeos" }, { - "Function": "Watch-GSDriveUpload", - "Service": null, - "Scope": null + "Function": "Get-GSCustomer", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.customer" }, { - "Function": "Get-GSChatMessage", - "Service": "Google.Apis.HangoutsChat.v1.HangoutsChatService", - "Scope": "https://www.googleapis.com/auth/chat.bot" + "Function": "Get-GSDomain", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.domain" }, { - "Function": "Get-GSOrganizationalUnit", + "Function": "Get-GSDomainAlias", "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.orgunit" + "Scope": "https://www.googleapis.com/auth/admin.directory.domain" }, { - "Function": "New-GSPresentationUpdateRequest", - "Service": null, - "Scope": null + "Function": "Get-GSGmailDelegate", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.group" }, { - "Function": "Import-GSSheet", - "Service": "Google.Apis.Sheets.v4.SheetsService", - "Scope": "https://www.googleapis.com/auth/drive" + "Function": "Get-GSGroup", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.group" }, { - "Function": "New-GSGmailSendAsAlias", - "Service": "Google.Apis.Gmail.v1.GmailService", - "Scope": "https://www.googleapis.com/auth/gmail.settings.sharing" + "Function": "Get-GSGroupAlias", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.group" }, { - "Function": "Update-GSDrive", - "Service": "Google.Apis.Drive.v3.DriveService", - "Scope": "https://www.googleapis.com/auth/drive" + "Function": "Get-GSGroupMember", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.group" }, { - "Function": "Remove-GSOrganizationalUnit", + "Function": "Get-GSGroupSettings", "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.orgunit" + "Scope": "https://www.googleapis.com/auth/admin.directory.group" }, { - "Function": "Copy-GSDriveFile", - "Service": "Google.Apis.Drive.v3.DriveService", - "Scope": "https://www.googleapis.com/auth/drive" + "Function": "Get-GSMobileDevice", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.device.mobile" }, { - "Function": "Remove-GSGmailSendAsAlias", - "Service": "Google.Apis.Gmail.v1.GmailService", - "Scope": "https://www.googleapis.com/auth/gmail.settings.sharing" + "Function": "Get-GSOrganizationalUnit", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.orgunit" }, { - "Function": "Restore-GSUser", + "Function": "Get-GSResource", "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.user" + "Scope": "https://www.googleapis.com/auth/admin.directory.resource.calendar" }, { - "Function": "Restore-GSUser", + "Function": "Get-GSUser", "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.user.readonly" + "Scope": "https://www.googleapis.com/auth/admin.directory.user" }, { - "Function": "New-GSGmailSMIMEInfo", - "Service": "Google.Apis.Gmail.v1.GmailService", - "Scope": "https://www.googleapis.com/auth/gmail.settings.basic" + "Function": "Get-GSUser", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.user.readonly" }, { - "Function": "New-GSGmailSMIMEInfo", - "Service": "Google.Apis.Gmail.v1.GmailService", - "Scope": "https://www.googleapis.com/auth/gmail.settings.sharing" + "Function": "Get-GSUserAlias", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.user" }, { - "Function": "Update-GSGroup", + "Function": "Get-GSUserAlias", "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.group" + "Scope": "https://www.googleapis.com/auth/admin.directory.user.readonly" }, { - "Function": "Get-GSGmailAutoForwardingSettings", - "Service": "Google.Apis.Gmail.v1.GmailService", - "Scope": "https://www.googleapis.com/auth/gmail.settings.basic" + "Function": "Get-GSUserASP", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.user" }, { - "Function": "Add-GSChatTextParagraph", - "Service": null, - "Scope": null + "Function": "Get-GSUserASP", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.user.security" }, { - "Function": "Get-GSUserAlias", + "Function": "Get-GSUserPhoto", "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", "Scope": "https://www.googleapis.com/auth/admin.directory.user" }, { - "Function": "Get-GSUserAlias", + "Function": "Get-GSUserPhoto", "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", "Scope": "https://www.googleapis.com/auth/admin.directory.user.readonly" }, { - "Function": "Remove-GSChatMessage", - "Service": "Google.Apis.HangoutsChat.v1.HangoutsChatService", - "Scope": "https://www.googleapis.com/auth/chat.bot" + "Function": "Get-GSUserSchema", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.user" }, { - "Function": "Add-GSChatOnClick", - "Service": null, - "Scope": null + "Function": "Get-GSUserSchema", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.userschema" }, { - "Function": "Update-GSGmailVacationSettings", - "Service": "Google.Apis.Gmail.v1.GmailService", - "Scope": "https://www.googleapis.com/auth/gmail.settings.basic" + "Function": "Get-GSUserToken", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.user" }, { - "Function": "Remove-GSGmailFilter", - "Service": "Google.Apis.Gmail.v1.GmailService", - "Scope": "https://www.googleapis.com/auth/gmail.settings.basic" + "Function": "Get-GSUserToken", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.user.security" }, { - "Function": "Add-GSChatKeyValue", - "Service": null, - "Scope": null + "Function": "Get-GSUserVerificationCodes", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.user" }, { - "Function": "Get-GSUsageReport", - "Service": "Google.Apis.Admin.Reports.reports_v1.ReportsService", - "Scope": "https://www.googleapis.com/auth/admin.reports.usage.readonly" + "Function": "Get-GSUserVerificationCodes", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.user.security" }, { - "Function": "Get-GSUserSchema", + "Function": "Invoke-GSUserOffboarding", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.device.mobile" + }, + { + "Function": "Invoke-GSUserOffboarding", "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", "Scope": "https://www.googleapis.com/auth/admin.directory.user" }, { - "Function": "Get-GSUserSchema", + "Function": "Invoke-GSUserOffboarding", "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.userschema" + "Scope": "https://www.googleapis.com/auth/admin.directory.user.readonly" }, { - "Function": "Get-GSGmailPopSettings", - "Service": "Google.Apis.Gmail.v1.GmailService", - "Scope": "https://www.googleapis.com/auth/gmail.settings.basic" + "Function": "Invoke-GSUserOffboarding", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.user.security" }, { - "Function": "New-GSDrive", - "Service": "Google.Apis.Drive.v3.DriveService", - "Scope": "https://www.googleapis.com/auth/drive" + "Function": "New-GSAdminRole", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.rolemanagement" }, { - "Function": "Add-GSCustomerPostalAddress", - "Service": null, - "Scope": null + "Function": "New-GSAdminRoleAssignment", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.rolemanagement" }, { - "Function": "Update-GSUserSchema", + "Function": "New-GSAdminRoleAssignment", "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", "Scope": "https://www.googleapis.com/auth/admin.directory.user" }, { - "Function": "Update-GSUserSchema", + "Function": "New-GSAdminRoleAssignment", "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.userschema" + "Scope": "https://www.googleapis.com/auth/admin.directory.user.readonly" }, { - "Function": "Get-GSDomain", + "Function": "New-GSDomain", "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", "Scope": "https://www.googleapis.com/auth/admin.directory.domain" }, { - "Function": "Get-GSGroupSettings", - "Service": "Google.Apis.Groupssettings.v1.GroupssettingsService", - "Scope": "https://www.googleapis.com/auth/apps.groups.settings" + "Function": "New-GSDomainAlias", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.domain" }, { - "Function": "Get-GSGroupSettings", + "Function": "New-GSGroup", "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", "Scope": "https://www.googleapis.com/auth/admin.directory.group" }, { - "Function": "Get-GSGmailFilter", - "Service": "Google.Apis.Gmail.v1.GmailService", - "Scope": "https://www.googleapis.com/auth/gmail.settings.basic" + "Function": "New-GSGroupAlias", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.group" }, { - "Function": "Get-GSStudentGuardian", - "Service": "Google.Apis.Classroom.v1.ClassroomService", - "Scope": "https://www.googleapis.com/auth/classroom.guardianlinks.students" + "Function": "New-GSOrganizationalUnit", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.orgunit" }, { - "Function": "Update-GSGmailSendAsAlias", - "Service": "Google.Apis.Gmail.v1.GmailService", - "Scope": "https://www.googleapis.com/auth/gmail.settings.basic" + "Function": "New-GSResource", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.resource.calendar" }, { - "Function": "Update-GSGmailSendAsAlias", - "Service": "Google.Apis.Gmail.v1.GmailService", - "Scope": "https://www.googleapis.com/auth/gmail.settings.sharing" + "Function": "New-GSUser", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.user" }, { - "Function": "Add-GSDocContent", - "Service": "Google.Apis.Drive.v3.DriveService", - "Scope": "https://www.googleapis.com/auth/drive" + "Function": "New-GSUserAlias", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.user" }, { - "Function": "Remove-GSUserToken", + "Function": "New-GSUserSchema", "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", "Scope": "https://www.googleapis.com/auth/admin.directory.user" }, { - "Function": "Remove-GSUserToken", + "Function": "New-GSUserSchema", "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.user.security" - }, - { - "Function": "Get-GSCourse", - "Service": "Google.Apis.Classroom.v1.ClassroomService", - "Scope": "https://www.googleapis.com/auth/classroom.courses" - }, - { - "Function": "Clear-PSGSuiteServiceCache", - "Service": null, - "Scope": null + "Scope": "https://www.googleapis.com/auth/admin.directory.userschema" }, { - "Function": "Get-GSGmailSendAsAlias", - "Service": "Google.Apis.Gmail.v1.GmailService", - "Scope": "https://www.googleapis.com/auth/gmail.settings.basic" + "Function": "New-GSUserVerificationCodes", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.user" }, { - "Function": "Set-PSGSuiteConfig", - "Service": null, - "Scope": null + "Function": "New-GSUserVerificationCodes", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.user.security" }, { - "Function": "Add-GSCalendarSubscription", - "Service": "Google.Apis.Calendar.v3.CalendarService", - "Scope": "https://www.googleapis.com/auth/calendar" + "Function": "Remove-GSAdminRole", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.rolemanagement" }, { - "Function": "Import-PSGSuiteConfig", - "Service": null, - "Scope": null + "Function": "Remove-GSAdminRoleAssignment", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.rolemanagement" }, { - "Function": "New-GSUserSchema", + "Function": "Remove-GSDomain", "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.user" + "Scope": "https://www.googleapis.com/auth/admin.directory.domain" }, { - "Function": "New-GSUserSchema", + "Function": "Remove-GSDomainAlias", "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.userschema" + "Scope": "https://www.googleapis.com/auth/admin.directory.domain" }, { - "Function": "Get-GSDriveFolderSize", - "Service": "Google.Apis.Drive.v3.DriveService", - "Scope": "https://www.googleapis.com/auth/drive" + "Function": "Remove-GSGroup", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.group" }, { - "Function": "Invoke-GSUserOffboarding", + "Function": "Remove-GSGroupAlias", "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.user" + "Scope": "https://www.googleapis.com/auth/admin.directory.group" }, { - "Function": "Invoke-GSUserOffboarding", + "Function": "Remove-GSGroupMember", "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.user.security" + "Scope": "https://www.googleapis.com/auth/admin.directory.group" }, { - "Function": "Invoke-GSUserOffboarding", + "Function": "Remove-GSMobileDevice", "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.user.readonly" + "Scope": "https://www.googleapis.com/auth/admin.directory.device.mobile" }, { - "Function": "Invoke-GSUserOffboarding", + "Function": "Remove-GSOrganizationalUnit", "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.device.mobile" + "Scope": "https://www.googleapis.com/auth/admin.directory.orgunit" }, { - "Function": "Invoke-GSUserOffboarding", - "Service": "Google.Apis.Licensing.v1.LicensingService", - "Scope": "https://www.googleapis.com/auth/apps.licensing" + "Function": "Remove-GSPrincipalGroupMembership", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.group" }, { - "Function": "Remove-GSDrive", - "Service": "Google.Apis.Drive.v3.DriveService", - "Scope": "https://www.googleapis.com/auth/drive" + "Function": "Remove-GSResource", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.resource.calendar" }, { - "Function": "Add-GSChatCard", - "Service": null, - "Scope": null + "Function": "Remove-GSUser", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.user" }, { - "Function": "Update-GSCourse", - "Service": "Google.Apis.Classroom.v1.ClassroomService", - "Scope": "https://www.googleapis.com/auth/classroom.courses" + "Function": "Remove-GSUserAlias", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.user" }, { - "Function": "Remove-GSCourseParticipant", - "Service": "Google.Apis.Classroom.v1.ClassroomService", - "Scope": "https://www.googleapis.com/auth/classroom.rosters" + "Function": "Remove-GSUserASP", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.user" }, { - "Function": "Get-GSDrivePermission", - "Service": "Google.Apis.Drive.v3.DriveService", - "Scope": "https://www.googleapis.com/auth/drive" + "Function": "Remove-GSUserASP", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.user.security" }, { - "Function": "Get-GSActivityReport", - "Service": "Google.Apis.Admin.Reports.reports_v1.ReportsService", - "Scope": "https://www.googleapis.com/auth/admin.reports.audit.readonly" + "Function": "Remove-GSUserPhoto", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.user" }, { - "Function": "Set-GSUserSchema", + "Function": "Remove-GSUserSchema", "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", "Scope": "https://www.googleapis.com/auth/admin.directory.user" }, { - "Function": "Set-GSUserSchema", + "Function": "Remove-GSUserSchema", "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", "Scope": "https://www.googleapis.com/auth/admin.directory.userschema" }, { - "Function": "Get-GSGmailLanguageSettings", - "Service": "Google.Apis.Gmail.v1.GmailService", - "Scope": "https://www.googleapis.com/auth/gmail.settings.basic" + "Function": "Remove-GSUserToken", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.user" }, { - "Function": "Revoke-GSStudentGuardianInvitation", - "Service": "Google.Apis.Classroom.v1.ClassroomService", - "Scope": "https://www.googleapis.com/auth/classroom.guardianlinks.students" + "Function": "Remove-GSUserToken", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.user.security" }, { - "Function": "Get-GSUserASP", + "Function": "Restore-GSUser", "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", "Scope": "https://www.googleapis.com/auth/admin.directory.user" }, { - "Function": "Get-GSUserASP", + "Function": "Restore-GSUser", "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.user.security" + "Scope": "https://www.googleapis.com/auth/admin.directory.user.readonly" }, { - "Function": "Get-GSDrive", - "Service": "Google.Apis.Drive.v3.DriveService", - "Scope": "https://www.googleapis.com/auth/drive" + "Function": "Revoke-GSUserVerificationCodes", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.user" }, { - "Function": "Add-GSUserPhone", - "Service": null, - "Scope": null + "Function": "Revoke-GSUserVerificationCodes", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.user.security" }, { - "Function": "Set-GSUserLicense", - "Service": "Google.Apis.Licensing.v1.LicensingService", - "Scope": "https://www.googleapis.com/auth/apps.licensing" + "Function": "Set-GSUserSchema", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.user" }, { - "Function": "Get-GSResource", + "Function": "Set-GSUserSchema", "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.resource.calendar" + "Scope": "https://www.googleapis.com/auth/admin.directory.userschema" }, { - "Function": "New-GSUserVerificationCodes", + "Function": "Start-GSDataTransfer", "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", "Scope": "https://www.googleapis.com/auth/admin.directory.user" }, { - "Function": "New-GSUserVerificationCodes", + "Function": "Start-GSDataTransfer", "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.user.security" + "Scope": "https://www.googleapis.com/auth/admin.directory.user.readonly" }, { - "Function": "Set-GSGroupSettings", - "Service": "Google.Apis.Groupssettings.v1.GroupssettingsService", - "Scope": "https://www.googleapis.com/auth/apps.groups.settings" + "Function": "Sync-GSUserCache", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.user" }, { - "Function": "New-GSDomain", + "Function": "Sync-GSUserCache", "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.domain" + "Scope": "https://www.googleapis.com/auth/admin.directory.user.readonly" }, { - "Function": "Get-PSGSuiteConfig", - "Service": null, - "Scope": null + "Function": "Test-GSGroupMembership", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.group" }, { - "Function": "New-GSCalendarACL", - "Service": "Google.Apis.Calendar.v3.CalendarService", - "Scope": "https://www.googleapis.com/auth/calendar" + "Function": "Update-GSAdminRole", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.rolemanagement" }, { - "Function": "Add-GSCourseParticipant", - "Service": "Google.Apis.Classroom.v1.ClassroomService", - "Scope": "https://www.googleapis.com/auth/classroom.rosters" + "Function": "Update-GSChromeOSDevice", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.device.chromeos" }, { - "Function": "Get-GSMobileDevice", + "Function": "Update-GSCustomer", "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.device.mobile" + "Scope": "https://www.googleapis.com/auth/admin.directory.customer" }, { - "Function": "Remove-GSGroupMember", + "Function": "Update-GSGroup", "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", "Scope": "https://www.googleapis.com/auth/admin.directory.group" }, { - "Function": "Get-GSStudentGuardianInvitation", - "Service": "Google.Apis.Classroom.v1.ClassroomService", - "Scope": "https://www.googleapis.com/auth/classroom.guardianlinks.students" - }, - { - "Function": "Get-GSSheetInfo", - "Service": "Google.Apis.Sheets.v4.SheetsService", - "Scope": "https://www.googleapis.com/auth/drive" + "Function": "Update-GSGroupMember", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.group" }, { - "Function": "Get-GSDocContent", - "Service": "Google.Apis.Drive.v3.DriveService", - "Scope": "https://www.googleapis.com/auth/drive" + "Function": "Update-GSGroupSettings", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.group" }, { - "Function": "Start-GSDataTransfer", + "Function": "Update-GSMobileDevice", "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.user" + "Scope": "https://www.googleapis.com/auth/admin.directory.device.mobile" }, { - "Function": "Start-GSDataTransfer", + "Function": "Update-GSOrganizationalUnit", "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.user.readonly" + "Scope": "https://www.googleapis.com/auth/admin.directory.orgunit" }, { - "Function": "Start-GSDataTransfer", - "Service": "Google.Apis.Admin.DataTransfer.datatransfer_v1.DataTransferService", - "Scope": "https://www.googleapis.com/auth/admin.datatransfer" + "Function": "Update-GSResource", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.resource.calendar" }, { "Function": "Update-GSUser", @@ -720,179 +710,179 @@ $script:_PSGSuiteOAuthScopes = @' "Scope": "https://www.googleapis.com/auth/admin.directory.user.readonly" }, { - "Function": "Remove-GSUserSchema", + "Function": "Update-GSUserPhoto", "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", "Scope": "https://www.googleapis.com/auth/admin.directory.user" }, { - "Function": "Remove-GSUserSchema", + "Function": "Update-GSUserSchema", "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.userschema" + "Scope": "https://www.googleapis.com/auth/admin.directory.user" }, { - "Function": "Add-GSGmailForwardingAddress", - "Service": "Google.Apis.Gmail.v1.GmailService", - "Scope": "https://www.googleapis.com/auth/gmail.settings.sharing" + "Function": "Update-GSUserSchema", + "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", + "Scope": "https://www.googleapis.com/auth/admin.directory.userschema" }, { - "Function": "Get-GSDriveFileList", - "Service": "Google.Apis.Drive.v3.DriveService", - "Scope": "https://www.googleapis.com/auth/drive" + "Function": "Get-GSActivityReport", + "Service": "Google.Apis.Admin.Reports.reports_v1.ReportsService", + "Scope": "https://www.googleapis.com/auth/admin.reports.audit.readonly" }, { - "Function": "Add-GSGmailSmtpMsa", - "Service": null, - "Scope": null + "Function": "Get-GSUsageReport", + "Service": "Google.Apis.Admin.Reports.reports_v1.ReportsService", + "Scope": "https://www.googleapis.com/auth/admin.reports.usage.readonly" }, { - "Function": "Get-GSGmailSMIMEInfo", - "Service": "Google.Apis.Gmail.v1.GmailService", - "Scope": "https://www.googleapis.com/auth/gmail.settings.basic" + "Function": "Add-GSCalendarSubscription", + "Service": "Google.Apis.Calendar.v3.CalendarService", + "Scope": "https://www.googleapis.com/auth/calendar" }, { - "Function": "Add-GSGmailFilter", - "Service": "Google.Apis.Gmail.v1.GmailService", - "Scope": "https://www.googleapis.com/auth/gmail.settings.basic" + "Function": "Get-GSCalendar", + "Service": "Google.Apis.Calendar.v3.CalendarService", + "Scope": "https://www.googleapis.com/auth/calendar" }, { - "Function": "Update-GSGmailSignature", - "Service": "Google.Apis.Gmail.v1.GmailService", - "Scope": "https://www.googleapis.com/auth/gmail.settings.basic" + "Function": "Get-GSCalendarACL", + "Service": "Google.Apis.Calendar.v3.CalendarService", + "Scope": "https://www.googleapis.com/auth/calendar" }, { - "Function": "Update-GSGmailSignature", - "Service": "Google.Apis.Gmail.v1.GmailService", - "Scope": "https://www.googleapis.com/auth/gmail.settings.sharing" + "Function": "Get-GSCalendarEvent", + "Service": "Google.Apis.Calendar.v3.CalendarService", + "Scope": "https://www.googleapis.com/auth/calendar" }, { - "Function": "Update-GSOrganizationalUnit", - "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.orgunit" + "Function": "Get-GSCalendarSubscription", + "Service": "Google.Apis.Calendar.v3.CalendarService", + "Scope": "https://www.googleapis.com/auth/calendar" }, { - "Function": "Compare-ModuleVersion", - "Service": null, - "Scope": null + "Function": "New-GSCalendarACL", + "Service": "Google.Apis.Calendar.v3.CalendarService", + "Scope": "https://www.googleapis.com/auth/calendar" }, { - "Function": "New-GSAdminRole", - "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.rolemanagement" + "Function": "New-GSCalendarEvent", + "Service": "Google.Apis.Calendar.v3.CalendarService", + "Scope": "https://www.googleapis.com/auth/calendar" }, { - "Function": "Confirm-GSCourseInvitation", - "Service": "Google.Apis.Classroom.v1.ClassroomService", - "Scope": "https://www.googleapis.com/auth/classroom.rosters" + "Function": "Remove-GSCalendarAcl", + "Service": "Google.Apis.Calendar.v3.CalendarService", + "Scope": "https://www.googleapis.com/auth/calendar" }, { - "Function": "Get-GSCalendarEvent", + "Function": "Remove-GSCalendarEvent", "Service": "Google.Apis.Calendar.v3.CalendarService", "Scope": "https://www.googleapis.com/auth/calendar" }, { - "Function": "Remove-GSCourse", - "Service": "Google.Apis.Classroom.v1.ClassroomService", - "Scope": "https://www.googleapis.com/auth/classroom.courses" + "Function": "Remove-GSCalendarSubscription", + "Service": "Google.Apis.Calendar.v3.CalendarService", + "Scope": "https://www.googleapis.com/auth/calendar" }, { - "Function": "Update-GSCalendarSubscription", + "Function": "Update-GSCalendarEvent", "Service": "Google.Apis.Calendar.v3.CalendarService", "Scope": "https://www.googleapis.com/auth/calendar" }, { - "Function": "Sync-GSUserCache", - "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.user" + "Function": "Update-GSCalendarSubscription", + "Service": "Google.Apis.Calendar.v3.CalendarService", + "Scope": "https://www.googleapis.com/auth/calendar" }, { - "Function": "Sync-GSUserCache", - "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.user.readonly" + "Function": "Add-GSCourseParticipant", + "Service": "Google.Apis.Classroom.v1.ClassroomService", + "Scope": "https://www.googleapis.com/auth/classroom.rosters" }, { - "Function": "Add-GSUserOrganization", - "Service": null, - "Scope": null + "Function": "Confirm-GSCourseInvitation", + "Service": "Google.Apis.Classroom.v1.ClassroomService", + "Scope": "https://www.googleapis.com/auth/classroom.rosters" }, { - "Function": "Update-GSUserLicense", - "Service": "Google.Apis.Licensing.v1.LicensingService", - "Scope": "https://www.googleapis.com/auth/apps.licensing" + "Function": "Get-GSClassroomUserProfile", + "Service": "Google.Apis.Classroom.v1.ClassroomService", + "Scope": "https://www.googleapis.com/auth/classroom.profile.emails" }, { - "Function": "Update-GSChatMessage", - "Service": "Google.Apis.HangoutsChat.v1.HangoutsChatService", - "Scope": "https://www.googleapis.com/auth/chat.bot" + "Function": "Get-GSClassroomUserProfile", + "Service": "Google.Apis.Classroom.v1.ClassroomService", + "Scope": "https://www.googleapis.com/auth/classroom.profile.photos" }, { - "Function": "Get-GSDataTransfer", - "Service": "Google.Apis.Admin.DataTransfer.datatransfer_v1.DataTransferService", - "Scope": "https://www.googleapis.com/auth/admin.datatransfer" + "Function": "Get-GSClassroomUserProfile", + "Service": "Google.Apis.Classroom.v1.ClassroomService", + "Scope": "https://www.googleapis.com/auth/classroom.rosters" }, { - "Function": "New-GSCourseInvitation", + "Function": "Get-GSCourse", "Service": "Google.Apis.Classroom.v1.ClassroomService", - "Scope": "https://www.googleapis.com/auth/classroom.rosters" + "Scope": "https://www.googleapis.com/auth/classroom.courses" }, { - "Function": "Revoke-GSUserVerificationCodes", - "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.user" + "Function": "Get-GSCourseAlias", + "Service": "Google.Apis.Classroom.v1.ClassroomService", + "Scope": "https://www.googleapis.com/auth/classroom.courses" }, { - "Function": "Revoke-GSUserVerificationCodes", - "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.user.security" + "Function": "Get-GSCourseInvitation", + "Service": "Google.Apis.Classroom.v1.ClassroomService", + "Scope": "https://www.googleapis.com/auth/classroom.rosters" }, { - "Function": "Stop-GSDriveFileUpload", - "Service": null, - "Scope": null + "Function": "Get-GSCourseParticipant", + "Service": "Google.Apis.Classroom.v1.ClassroomService", + "Scope": "https://www.googleapis.com/auth/classroom.profile.emails" }, { - "Function": "Remove-GSGmailSMIMEInfo", - "Service": "Google.Apis.Gmail.v1.GmailService", - "Scope": "https://www.googleapis.com/auth/gmail.settings.basic" + "Function": "Get-GSCourseParticipant", + "Service": "Google.Apis.Classroom.v1.ClassroomService", + "Scope": "https://www.googleapis.com/auth/classroom.profile.photos" }, { - "Function": "Edit-GSPresentation", - "Service": "Google.Apis.Slides.v1.SlidesService", - "Scope": "https://www.googleapis.com/auth/drive" + "Function": "Get-GSCourseParticipant", + "Service": "Google.Apis.Classroom.v1.ClassroomService", + "Scope": "https://www.googleapis.com/auth/classroom.rosters" }, { - "Function": "Edit-GSPresentation", - "Service": "Google.Apis.Drive.v3.DriveService", - "Scope": "https://www.googleapis.com/auth/drive" + "Function": "Get-GSStudentGuardian", + "Service": "Google.Apis.Classroom.v1.ClassroomService", + "Scope": "https://www.googleapis.com/auth/classroom.guardianlinks.students" }, { - "Function": "Add-GSChatButton", - "Service": null, - "Scope": null + "Function": "Get-GSStudentGuardianInvitation", + "Service": "Google.Apis.Classroom.v1.ClassroomService", + "Scope": "https://www.googleapis.com/auth/classroom.guardianlinks.students" }, { - "Function": "Get-GSClassroomUserProfile", + "Function": "New-GSCourse", "Service": "Google.Apis.Classroom.v1.ClassroomService", - "Scope": "https://www.googleapis.com/auth/classroom.profile.emails" + "Scope": "https://www.googleapis.com/auth/classroom.courses" }, { - "Function": "Get-GSClassroomUserProfile", + "Function": "New-GSCourseAlias", "Service": "Google.Apis.Classroom.v1.ClassroomService", - "Scope": "https://www.googleapis.com/auth/classroom.profile.photos" + "Scope": "https://www.googleapis.com/auth/classroom.courses" }, { - "Function": "Get-GSClassroomUserProfile", + "Function": "New-GSCourseInvitation", "Service": "Google.Apis.Classroom.v1.ClassroomService", "Scope": "https://www.googleapis.com/auth/classroom.rosters" }, { - "Function": "Get-GSGmailDelegate", - "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.group" + "Function": "New-GSStudentGuardianInvitation", + "Service": "Google.Apis.Classroom.v1.ClassroomService", + "Scope": "https://www.googleapis.com/auth/classroom.guardianlinks.students" }, { - "Function": "Get-GSGmailDelegate", - "Service": "Google.Apis.Gmail.v1.GmailService", - "Scope": "https://www.googleapis.com/auth/gmail.settings.basic" + "Function": "Remove-GSCourse", + "Service": "Google.Apis.Classroom.v1.ClassroomService", + "Scope": "https://www.googleapis.com/auth/classroom.courses" }, { "Function": "Remove-GSCourseAlias", @@ -905,419 +895,429 @@ $script:_PSGSuiteOAuthScopes = @' "Scope": "https://www.googleapis.com/auth/classroom.rosters" }, { - "Function": "Get-GSCalendarSubscription", - "Service": "Google.Apis.Calendar.v3.CalendarService", - "Scope": "https://www.googleapis.com/auth/calendar" + "Function": "Remove-GSCourseParticipant", + "Service": "Google.Apis.Classroom.v1.ClassroomService", + "Scope": "https://www.googleapis.com/auth/classroom.rosters" }, { - "Function": "Get-GSCourseParticipant", + "Function": "Remove-GSStudentGuardian", "Service": "Google.Apis.Classroom.v1.ClassroomService", - "Scope": "https://www.googleapis.com/auth/classroom.profile.emails" + "Scope": "https://www.googleapis.com/auth/classroom.guardianlinks.students" }, { - "Function": "Get-GSCourseParticipant", + "Function": "Revoke-GSStudentGuardianInvitation", "Service": "Google.Apis.Classroom.v1.ClassroomService", - "Scope": "https://www.googleapis.com/auth/classroom.profile.photos" + "Scope": "https://www.googleapis.com/auth/classroom.guardianlinks.students" }, { - "Function": "Get-GSCourseParticipant", + "Function": "Update-GSCourse", "Service": "Google.Apis.Classroom.v1.ClassroomService", - "Scope": "https://www.googleapis.com/auth/classroom.rosters" + "Scope": "https://www.googleapis.com/auth/classroom.courses" }, { - "Function": "New-GSUserAlias", - "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.user" + "Function": "Add-GSDocContent", + "Service": "Google.Apis.Drive.v3.DriveService", + "Scope": "https://www.googleapis.com/auth/drive" }, { - "Function": "Show-GSDrive", + "Function": "Add-GSDrivePermission", "Service": "Google.Apis.Drive.v3.DriveService", "Scope": "https://www.googleapis.com/auth/drive" }, { - "Function": "Copy-GSSheet", - "Service": "Google.Apis.Sheets.v4.SheetsService", + "Function": "Copy-GSDriveFile", + "Service": "Google.Apis.Drive.v3.DriveService", "Scope": "https://www.googleapis.com/auth/drive" }, { - "Function": "Get-GSPresentation", - "Service": "Google.Apis.Slides.v1.SlidesService", + "Function": "Edit-GSPresentation", + "Service": "Google.Apis.Drive.v3.DriveService", "Scope": "https://www.googleapis.com/auth/drive" }, { - "Function": "Get-GSPresentation", + "Function": "Export-GSDriveFile", "Service": "Google.Apis.Drive.v3.DriveService", "Scope": "https://www.googleapis.com/auth/drive" }, { - "Function": "Update-GSChromeOSDevice", - "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.device.chromeos" + "Function": "Get-GSDocContent", + "Service": "Google.Apis.Drive.v3.DriveService", + "Scope": "https://www.googleapis.com/auth/drive" }, { - "Function": "Remove-GSCalendarSubscription", - "Service": "Google.Apis.Calendar.v3.CalendarService", - "Scope": "https://www.googleapis.com/auth/calendar" + "Function": "Get-GSDrive", + "Service": "Google.Apis.Drive.v3.DriveService", + "Scope": "https://www.googleapis.com/auth/drive" }, { - "Function": "Remove-GSUser", - "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.user" + "Function": "Get-GSDriveFile", + "Service": "Google.Apis.Drive.v3.DriveService", + "Scope": "https://www.googleapis.com/auth/drive" }, { - "Function": "New-GSOrganizationalUnit", - "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.orgunit" + "Function": "Get-GSDriveFileList", + "Service": "Google.Apis.Drive.v3.DriveService", + "Scope": "https://www.googleapis.com/auth/drive" }, { - "Function": "Add-GSUserLocation", - "Service": null, - "Scope": null + "Function": "Get-GSDriveFolderSize", + "Service": "Google.Apis.Drive.v3.DriveService", + "Scope": "https://www.googleapis.com/auth/drive" }, { - "Function": "Add-GSGmailDelegate", - "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.group" + "Function": "Get-GSDrivePermission", + "Service": "Google.Apis.Drive.v3.DriveService", + "Scope": "https://www.googleapis.com/auth/drive" }, { - "Function": "Add-GSGmailDelegate", - "Service": "Google.Apis.Gmail.v1.GmailService", - "Scope": "https://www.googleapis.com/auth/gmail.settings.sharing" + "Function": "Get-GSDriveProfile", + "Service": "Google.Apis.Drive.v3.DriveService", + "Scope": "https://www.googleapis.com/auth/drive" }, { - "Function": "Remove-GSDomainAlias", - "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.domain" + "Function": "Get-GSDriveRevision", + "Service": "Google.Apis.Drive.v3.DriveService", + "Scope": "https://www.googleapis.com/auth/drive" }, { - "Function": "Update-GSDriveFile", + "Function": "Get-GSPresentation", "Service": "Google.Apis.Drive.v3.DriveService", "Scope": "https://www.googleapis.com/auth/drive" }, { - "Function": "Add-GSChatCardSection", - "Service": null, - "Scope": null + "Function": "Hide-GSDrive", + "Service": "Google.Apis.Drive.v3.DriveService", + "Scope": "https://www.googleapis.com/auth/drive" }, { - "Function": "Add-GSCalendarNotification", - "Service": null, - "Scope": null + "Function": "New-GSDrive", + "Service": "Google.Apis.Drive.v3.DriveService", + "Scope": "https://www.googleapis.com/auth/drive" }, { - "Function": "Update-GSGmailLanguageSettings", - "Service": "Google.Apis.Gmail.v1.GmailService", - "Scope": "https://www.googleapis.com/auth/gmail.settings.basic" - }, - { - "Function": "Add-GSUserSchemaField", - "Service": null, - "Scope": null + "Function": "New-GSDriveFile", + "Service": "Google.Apis.Drive.v3.DriveService", + "Scope": "https://www.googleapis.com/auth/drive" }, { - "Function": "Get-GSGmailForwardingAddress", - "Service": "Google.Apis.Gmail.v1.GmailService", - "Scope": "https://www.googleapis.com/auth/gmail.settings.basic" + "Function": "Remove-GSDrive", + "Service": "Google.Apis.Drive.v3.DriveService", + "Scope": "https://www.googleapis.com/auth/drive" }, { - "Function": "Add-GSSheetValues", - "Service": "Google.Apis.Sheets.v4.SheetsService", + "Function": "Remove-GSDriveFile", + "Service": "Google.Apis.Drive.v3.DriveService", "Scope": "https://www.googleapis.com/auth/drive" }, { - "Function": "Get-PSGSuiteServiceCache", - "Service": null, - "Scope": null + "Function": "Remove-GSDrivePermission", + "Service": "Google.Apis.Drive.v3.DriveService", + "Scope": "https://www.googleapis.com/auth/drive" }, { - "Function": "New-GSStudentGuardianInvitation", - "Service": "Google.Apis.Classroom.v1.ClassroomService", - "Scope": "https://www.googleapis.com/auth/classroom.guardianlinks.students" + "Function": "Remove-GSDriveRevision", + "Service": "Google.Apis.Drive.v3.DriveService", + "Scope": "https://www.googleapis.com/auth/drive" }, { - "Function": "New-GSCourseAlias", - "Service": "Google.Apis.Classroom.v1.ClassroomService", - "Scope": "https://www.googleapis.com/auth/classroom.courses" + "Function": "Set-GSDocContent", + "Service": "Google.Apis.Drive.v3.DriveService", + "Scope": "https://www.googleapis.com/auth/drive" }, { - "Function": "Add-GSChatCardAction", - "Service": null, - "Scope": null + "Function": "Show-GSDrive", + "Service": "Google.Apis.Drive.v3.DriveService", + "Scope": "https://www.googleapis.com/auth/drive" }, { - "Function": "Remove-GSDomain", - "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.domain" + "Function": "Start-GSDriveFileUpload", + "Service": "Google.Apis.Drive.v3.DriveService", + "Scope": "https://www.googleapis.com/auth/drive" }, { - "Function": "Get-GSDriveProfile", + "Function": "Update-GSDrive", "Service": "Google.Apis.Drive.v3.DriveService", "Scope": "https://www.googleapis.com/auth/drive" }, { - "Function": "Hide-GSDrive", + "Function": "Update-GSDriveFile", "Service": "Google.Apis.Drive.v3.DriveService", "Scope": "https://www.googleapis.com/auth/drive" }, { - "Function": "New-GSSheet", - "Service": "Google.Apis.Sheets.v4.SheetsService", + "Function": "Update-GSDriveRevision", + "Service": "Google.Apis.Drive.v3.DriveService", "Scope": "https://www.googleapis.com/auth/drive" }, { - "Function": "New-GSCalendarEvent", - "Service": "Google.Apis.Calendar.v3.CalendarService", - "Scope": "https://www.googleapis.com/auth/calendar" + "Function": "Add-GSGmailDelegate", + "Service": "Google.Apis.Gmail.v1.GmailService", + "Scope": "https://www.googleapis.com/auth/gmail.settings.sharing" }, { - "Function": "Get-PSGSuiteOAuthScope", - "Service": null, - "Scope": null + "Function": "Add-GSGmailFilter", + "Service": "Google.Apis.Gmail.v1.GmailService", + "Scope": "https://www.googleapis.com/auth/gmail.settings.basic" }, { - "Function": "Get-GSUserPhoto", - "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.user" + "Function": "Add-GSGmailForwardingAddress", + "Service": "Google.Apis.Gmail.v1.GmailService", + "Scope": "https://www.googleapis.com/auth/gmail.settings.sharing" }, { - "Function": "Get-GSUserPhoto", - "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.user.readonly" + "Function": "Get-GSGmailAutoForwardingSettings", + "Service": "Google.Apis.Gmail.v1.GmailService", + "Scope": "https://www.googleapis.com/auth/gmail.settings.basic" }, { - "Function": "Switch-PSGSuiteConfig", - "Service": null, - "Scope": null + "Function": "Get-GSGmailDelegate", + "Service": "Google.Apis.Gmail.v1.GmailService", + "Scope": "https://www.googleapis.com/auth/gmail.settings.basic" }, { - "Function": "Update-GSGmailAutoForwardingSettings", + "Function": "Get-GSGmailFilter", "Service": "Google.Apis.Gmail.v1.GmailService", - "Scope": "https://www.googleapis.com/auth/gmail.settings.sharing" + "Scope": "https://www.googleapis.com/auth/gmail.settings.basic" }, { - "Function": "Export-GSSheet", - "Service": "Google.Apis.Sheets.v4.SheetsService", - "Scope": "https://www.googleapis.com/auth/drive" + "Function": "Get-GSGmailForwardingAddress", + "Service": "Google.Apis.Gmail.v1.GmailService", + "Scope": "https://www.googleapis.com/auth/gmail.settings.basic" }, { - "Function": "Get-GSUser", - "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.user" + "Function": "Get-GSGmailImapSettings", + "Service": "Google.Apis.Gmail.v1.GmailService", + "Scope": "https://www.googleapis.com/auth/gmail.settings.basic" }, { - "Function": "Get-GSUser", - "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.user.readonly" + "Function": "Get-GSGmailLanguageSettings", + "Service": "Google.Apis.Gmail.v1.GmailService", + "Scope": "https://www.googleapis.com/auth/gmail.settings.basic" }, { - "Function": "Add-GSDrivePermission", - "Service": "Google.Apis.Drive.v3.DriveService", - "Scope": "https://www.googleapis.com/auth/drive" + "Function": "Get-GSGmailPopSettings", + "Service": "Google.Apis.Gmail.v1.GmailService", + "Scope": "https://www.googleapis.com/auth/gmail.settings.basic" }, { - "Function": "New-GSResource", - "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.resource.calendar" + "Function": "Get-GSGmailSendAsAlias", + "Service": "Google.Apis.Gmail.v1.GmailService", + "Scope": "https://www.googleapis.com/auth/gmail.settings.basic" }, { - "Function": "Get-GSDomainAlias", - "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.domain" + "Function": "Get-GSGmailSMIMEInfo", + "Service": "Google.Apis.Gmail.v1.GmailService", + "Scope": "https://www.googleapis.com/auth/gmail.settings.basic" }, { - "Function": "Export-PSGSuiteConfig", - "Service": null, - "Scope": null + "Function": "Get-GSGmailVacationSettings", + "Service": "Google.Apis.Gmail.v1.GmailService", + "Scope": "https://www.googleapis.com/auth/gmail.settings.basic" }, { - "Function": "Remove-GSResource", - "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.resource.calendar" + "Function": "New-GSGmailSendAsAlias", + "Service": "Google.Apis.Gmail.v1.GmailService", + "Scope": "https://www.googleapis.com/auth/gmail.settings.sharing" }, { - "Function": "Get-GSGroup", - "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.group" + "Function": "New-GSGmailSMIMEInfo", + "Service": "Google.Apis.Gmail.v1.GmailService", + "Scope": "https://www.googleapis.com/auth/gmail.settings.basic" }, { - "Function": "Get-GSChromeOSDevice", - "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.device.chromeos" + "Function": "New-GSGmailSMIMEInfo", + "Service": "Google.Apis.Gmail.v1.GmailService", + "Scope": "https://www.googleapis.com/auth/gmail.settings.sharing" }, { - "Function": "Update-GSCustomer", - "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.customer" + "Function": "Remove-GSGmailDelegate", + "Service": "Google.Apis.Gmail.v1.GmailService", + "Scope": "https://www.googleapis.com/auth/gmail.settings.sharing" }, { - "Function": "Update-GSAdminRole", - "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.rolemanagement" + "Function": "Remove-GSGmailFilter", + "Service": "Google.Apis.Gmail.v1.GmailService", + "Scope": "https://www.googleapis.com/auth/gmail.settings.basic" }, { - "Function": "New-GSAdminRoleAssignment", - "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.rolemanagement" + "Function": "Remove-GSGmailSendAsAlias", + "Service": "Google.Apis.Gmail.v1.GmailService", + "Scope": "https://www.googleapis.com/auth/gmail.settings.sharing" }, { - "Function": "New-GSAdminRoleAssignment", - "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.user" + "Function": "Remove-GSGmailSMIMEInfo", + "Service": "Google.Apis.Gmail.v1.GmailService", + "Scope": "https://www.googleapis.com/auth/gmail.settings.basic" }, { - "Function": "New-GSAdminRoleAssignment", - "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.user.readonly" + "Function": "Send-GSGmailSendAsConfirmation", + "Service": "Google.Apis.Gmail.v1.GmailService", + "Scope": "https://www.googleapis.com/auth/gmail.settings.sharing" }, { - "Function": "Get-GSCourseAlias", - "Service": "Google.Apis.Classroom.v1.ClassroomService", - "Scope": "https://www.googleapis.com/auth/classroom.courses" + "Function": "Update-GSGmailAutoForwardingSettings", + "Service": "Google.Apis.Gmail.v1.GmailService", + "Scope": "https://www.googleapis.com/auth/gmail.settings.sharing" }, { - "Function": "Get-GSGroupMember", - "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.group" + "Function": "Update-GSGmailImapSettings", + "Service": "Google.Apis.Gmail.v1.GmailService", + "Scope": "https://www.googleapis.com/auth/gmail.settings.basic" }, { - "Function": "Get-GSChatConfig", - "Service": null, - "Scope": null + "Function": "Update-GSGmailLanguageSettings", + "Service": "Google.Apis.Gmail.v1.GmailService", + "Scope": "https://www.googleapis.com/auth/gmail.settings.basic" }, { - "Function": "Add-GSUserExternalId", - "Service": null, - "Scope": null + "Function": "Update-GSGmailPopSettings", + "Service": "Google.Apis.Gmail.v1.GmailService", + "Scope": "https://www.googleapis.com/auth/gmail.settings.basic" }, { - "Function": "Get-GSCalendar", - "Service": "Google.Apis.Calendar.v3.CalendarService", - "Scope": "https://www.googleapis.com/auth/calendar" + "Function": "Update-GSGmailSendAsAlias", + "Service": "Google.Apis.Gmail.v1.GmailService", + "Scope": "https://www.googleapis.com/auth/gmail.settings.basic" }, { - "Function": "Add-GSGroupMember", - "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.group" + "Function": "Update-GSGmailSendAsAlias", + "Service": "Google.Apis.Gmail.v1.GmailService", + "Scope": "https://www.googleapis.com/auth/gmail.settings.sharing" }, { - "Function": "Get-GSUserVerificationCodes", - "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.user" + "Function": "Update-GSGmailSignature", + "Service": "Google.Apis.Gmail.v1.GmailService", + "Scope": "https://www.googleapis.com/auth/gmail.settings.basic" }, { - "Function": "Get-GSUserVerificationCodes", - "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.user.security" + "Function": "Update-GSGmailSignature", + "Service": "Google.Apis.Gmail.v1.GmailService", + "Scope": "https://www.googleapis.com/auth/gmail.settings.sharing" }, { - "Function": "New-GSUser", - "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.user" + "Function": "Update-GSGmailVacationSettings", + "Service": "Google.Apis.Gmail.v1.GmailService", + "Scope": "https://www.googleapis.com/auth/gmail.settings.basic" }, { - "Function": "Test-GSGroupMembership", - "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.group" + "Function": "Get-GSGroupSettings", + "Service": "Google.Apis.Groupssettings.v1.GroupssettingsService", + "Scope": "https://www.googleapis.com/auth/apps.groups.settings" }, { - "Function": "Add-GSEventAttendee", - "Service": null, - "Scope": null + "Function": "Set-GSGroupSettings", + "Service": "Google.Apis.Groupssettings.v1.GroupssettingsService", + "Scope": "https://www.googleapis.com/auth/apps.groups.settings" }, { - "Function": "Remove-GSPrincipalGroupMembership", - "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.group" + "Function": "Update-GSGroupSettings", + "Service": "Google.Apis.Groupssettings.v1.GroupssettingsService", + "Scope": "https://www.googleapis.com/auth/apps.groups.settings" }, { - "Function": "Get-GSAdminRoleAssignment", - "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.rolemanagement" + "Function": "Get-GSChatMember", + "Service": "Google.Apis.HangoutsChat.v1.HangoutsChatService", + "Scope": "https://www.googleapis.com/auth/chat.bot" }, { - "Function": "Get-GSAdminRoleAssignment", - "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.rolemanagement.readonly" + "Function": "Get-GSChatMessage", + "Service": "Google.Apis.HangoutsChat.v1.HangoutsChatService", + "Scope": "https://www.googleapis.com/auth/chat.bot" }, { - "Function": "Get-GSAdminRoleAssignment", - "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.user" + "Function": "Get-GSChatSpace", + "Service": "Google.Apis.HangoutsChat.v1.HangoutsChatService", + "Scope": "https://www.googleapis.com/auth/chat.bot" }, { - "Function": "Get-GSAdminRoleAssignment", - "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.user.readonly" + "Function": "Remove-GSChatMessage", + "Service": "Google.Apis.HangoutsChat.v1.HangoutsChatService", + "Scope": "https://www.googleapis.com/auth/chat.bot" }, { - "Function": "Add-GSUserEmail", - "Service": null, - "Scope": null + "Function": "Send-GSChatMessage", + "Service": "Google.Apis.HangoutsChat.v1.HangoutsChatService", + "Scope": "https://www.googleapis.com/auth/chat.bot" }, { - "Function": "Add-GSUserIm", - "Service": null, - "Scope": null + "Function": "Update-GSChatMessage", + "Service": "Google.Apis.HangoutsChat.v1.HangoutsChatService", + "Scope": "https://www.googleapis.com/auth/chat.bot" }, { - "Function": "Update-GSGroupSettings", - "Service": "Google.Apis.Groupssettings.v1.GroupssettingsService", - "Scope": "https://www.googleapis.com/auth/apps.groups.settings" + "Function": "Get-GSUserLicense", + "Service": "Google.Apis.Licensing.v1.LicensingService", + "Scope": "https://www.googleapis.com/auth/apps.licensing" }, { - "Function": "Update-GSGroupSettings", - "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.group" + "Function": "Invoke-GSUserOffboarding", + "Service": "Google.Apis.Licensing.v1.LicensingService", + "Scope": "https://www.googleapis.com/auth/apps.licensing" }, { - "Function": "Get-GSGmailImapSettings", - "Service": "Google.Apis.Gmail.v1.GmailService", - "Scope": "https://www.googleapis.com/auth/gmail.settings.basic" + "Function": "Remove-GSUserLicense", + "Service": "Google.Apis.Licensing.v1.LicensingService", + "Scope": "https://www.googleapis.com/auth/apps.licensing" }, { - "Function": "Get-GSAdminRole", - "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.rolemanagement" + "Function": "Set-GSUserLicense", + "Service": "Google.Apis.Licensing.v1.LicensingService", + "Scope": "https://www.googleapis.com/auth/apps.licensing" }, { - "Function": "Add-GSChatImage", - "Service": null, - "Scope": null + "Function": "Update-GSUserLicense", + "Service": "Google.Apis.Licensing.v1.LicensingService", + "Scope": "https://www.googleapis.com/auth/apps.licensing" }, { - "Function": "Add-GSUserRelation", - "Service": null, - "Scope": null + "Function": "Add-GSSheetValues", + "Service": "Google.Apis.Sheets.v4.SheetsService", + "Scope": "https://www.googleapis.com/auth/drive" }, { - "Function": "Get-GSChatMember", - "Service": "Google.Apis.HangoutsChat.v1.HangoutsChatService", - "Scope": "https://www.googleapis.com/auth/chat.bot" + "Function": "Clear-GSSheet", + "Service": "Google.Apis.Sheets.v4.SheetsService", + "Scope": "https://www.googleapis.com/auth/drive" }, { - "Function": "Send-GSGmailSendAsConfirmation", - "Service": "Google.Apis.Gmail.v1.GmailService", - "Scope": "https://www.googleapis.com/auth/gmail.settings.sharing" + "Function": "Copy-GSSheet", + "Service": "Google.Apis.Sheets.v4.SheetsService", + "Scope": "https://www.googleapis.com/auth/drive" }, { - "Function": "New-GoogleService", - "Service": null, - "Scope": null + "Function": "Export-GSSheet", + "Service": "Google.Apis.Sheets.v4.SheetsService", + "Scope": "https://www.googleapis.com/auth/drive" }, { - "Function": "Remove-GSMobileDevice", - "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.device.mobile" + "Function": "Get-GSSheetInfo", + "Service": "Google.Apis.Sheets.v4.SheetsService", + "Scope": "https://www.googleapis.com/auth/drive" }, { - "Function": "Get-GSDataTransferApplication", - "Service": "Google.Apis.Admin.DataTransfer.datatransfer_v1.DataTransferService", - "Scope": "https://www.googleapis.com/auth/admin.datatransfer" + "Function": "Import-GSSheet", + "Service": "Google.Apis.Sheets.v4.SheetsService", + "Scope": "https://www.googleapis.com/auth/drive" }, { - "Function": "Get-GSCustomer", - "Service": "Google.Apis.Admin.Directory.directory_v1.DirectoryService", - "Scope": "https://www.googleapis.com/auth/admin.directory.customer" + "Function": "New-GSSheet", + "Service": "Google.Apis.Sheets.v4.SheetsService", + "Scope": "https://www.googleapis.com/auth/drive" + }, + { + "Function": "Update-GSSheet", + "Service": "Google.Apis.Sheets.v4.SheetsService", + "Scope": "https://www.googleapis.com/auth/drive" + }, + { + "Function": "Edit-GSPresentation", + "Service": "Google.Apis.Slides.v1.SlidesService", + "Scope": "https://www.googleapis.com/auth/drive" + }, + { + "Function": "Get-GSPresentation", + "Service": "Google.Apis.Slides.v1.SlidesService", + "Scope": "https://www.googleapis.com/auth/drive" } ] '@ | ConvertFrom-Json diff --git a/ci/templates/OAuthScopes.ps1 b/ci/templates/OAuthScopes.ps1 index eede2733..3402e964 100644 --- a/ci/templates/OAuthScopes.ps1 +++ b/ci/templates/OAuthScopes.ps1 @@ -185,9 +185,15 @@ ForEach ($FunctionName in $Script:FunctionScopes.keys){ } # Generate datasets that will be used to validate function parameters -$ValidServices = $OutputScopes | Select-Object -ExpandProperty 'Service' -Unique -$ValidFunctions = $OutputScopes | Select-Object -ExpandProperty 'Function' -Unique -$ValidScopes = $OutputScopes | Select-Object -ExpandProperty 'Scope' -Unique +$OutputScopes = $OutputScopes | Sort-Object -Property Service, Function, Scope +$ValidServices = $OutputScopes | Select-Object -ExpandProperty 'Service' -Unique | Sort-Object +$ValidFunctions = $OutputScopes | Select-Object -ExpandProperty 'Function' -Unique | Sort-Object +$ValidScopes = $OutputScopes | Select-Object -ExpandProperty 'Scope' -Unique | Sort-Object +$ValidAllValues = @( + $ValidServices + $ValidFunctions + $ValidScopes +) # Return the output $HashOutput = @{} @@ -248,4 +254,18 @@ class PSGSuiteValidOAuthScopeValues : System.Management.Automation.IValidateSetV $HashOutput['\Class\PSGSuiteValidOAuthScopeValues.ps1'] = $Code +# \Class\PSGSuiteValidClientSecretOAuthScopeValues +$Code = @" +class PSGSuiteValidClientSecretOAuthScopeValues : System.Management.Automation.IValidateSetValuesGenerator { + [string[]] GetValidValues() { + `$Values = @( + '$($ValidAllValues -join "',`n '")' + ) + return `$Values + } +} +"@ +$HashOutput['\Class\PSGSuiteValidClientSecretOAuthScopeValues.ps1'] = $Code + + $HashOutput From 4c0408c7ef581dfa41e3315b119fd2c0f14b5dd9 Mon Sep 17 00:00:00 2001 From: Foggy2 <23325075+Foggy2@users.noreply.github.com> Date: Mon, 23 Jun 2025 16:34:25 +0930 Subject: [PATCH 11/13] Added `ClientSecretOAuthScopes` to the configuration schema --- CHANGELOG.md | 6 ++-- PSGSuite/Private/EncryptionHelpers.ps1 | 10 +++++++ .../Configuration/Export-PSGSuiteConfig.ps1 | 2 +- .../Configuration/Set-PSGSuiteConfig.ps1 | 29 ++++++++++++++++++- 4 files changed, 43 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 75d767cc..cf997508 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -134,13 +134,15 @@ - Added `PSGSuiteValidServiceValues` class that contains and validates the Google API service names that are used by PSGSuite. eg, `Google.Apis.Slides.v1.SlidesService` - Added `PSGSuiteValidFunctionValues` class that contains and validates the list of public PSGSuite function names. eg, `Get-GSPresentation` - Added `PSGSuiteValidOAuthScopeValues` class that contains and validates the list of OAuth scopes that are used by PSGSuite. eg, `https://www.googleapis.com/auth/drive` +- Added `PSGSuiteValidClientSecretOAuthScopeValues` class that contains and validates all valid values from `PSGSuityeValidServiceValues`, `PSGSuiteValidFunctionValues` & `PSGSuiteValidOAuthScopeValues`. - Added `ci\templates\OAuthScopes.ps1` generation template that scans the PSGSuite source directory for the OAuth scopes, function names and Google API service names that are used by PSGSuite. The discovered data is then used to programmatically produce the following items: - `Module\OAuthScopes.ps1` - Contains the module variable `$script:_PSGSuiteOAuthScopes` that contains the dataset used by `Get-PSGSuiteOAuthScope` - `Class\PSGSuiteValidServiceValues.ps1` - `Class\PSGSuiteValidFunctionValues.ps1` - `Class\PSGSuiteValidOAuthScopeValues.ps1` - - + - `Class\PSGSuiteValidClientSecretOAuthScopeValues.ps1` +- Added `ClientSecretOAuthScopes` property to the module configuration schema. This property defines the list of OAuth scopes that are requested by default when Client Secrets authentication is used. If no OAuth scopes are specified or a command requires an OAuth scope that is not included in this list, PSGSuite will fallback to requesting each additional OAuth scope when they are used. +- Added `-ClientSecretOAuthScopes` parameter to `Set-PSGSuiteConfig`. This parameter is validated and accepts any valid Function, API service, or OAuth scope that is used by PSGSuite. eg, `Get-GSPresentation`, `https://www.googleapis.com/auth/drive` or `Google.Apis.Slides.v1.SlidesService`. ## 3.0.0 - 2024-11-20 diff --git a/PSGSuite/Private/EncryptionHelpers.ps1 b/PSGSuite/Private/EncryptionHelpers.ps1 index e94d01fd..f12efe08 100644 --- a/PSGSuite/Private/EncryptionHelpers.ps1 +++ b/PSGSuite/Private/EncryptionHelpers.ps1 @@ -22,6 +22,16 @@ function Get-GSDecryptedConfig { @{l = 'JSONServiceAccountKey'; e = {Invoke-GSDecrypt $_.JSONServiceAccountKey}} @{l = 'ClientSecretsPath'; e = { Invoke-GSDecrypt $_.ClientSecretsPath } } @{l = 'ClientSecrets'; e = { Invoke-GSDecrypt $_.ClientSecrets } } + @{l = 'ClientSecretOAuthScopes'; e = { + $array = @() + If ($_.ClientSecretOAuthScopes){ + foreach ($entry in $_.ClientSecretOAuthScopes){ + $array += Invoke-GSDecrypt $Entry + } + } + $array + } + } @{l = 'AppEmail'; e = { if ($_.JSONServiceAccountKey) { (Invoke-GSDecrypt $_.JSONServiceAccountKey | ConvertFrom-Json).client_email diff --git a/PSGSuite/Public/Configuration/Export-PSGSuiteConfig.ps1 b/PSGSuite/Public/Configuration/Export-PSGSuiteConfig.ps1 index 9d7172a9..e1e9ec56 100644 --- a/PSGSuite/Public/Configuration/Export-PSGSuiteConfig.ps1 +++ b/PSGSuite/Public/Configuration/Export-PSGSuiteConfig.ps1 @@ -41,7 +41,7 @@ function Export-PSGSuiteConfig { Process { try { Write-Verbose "Exporting config '$ConfigName' to path: $Path" - $baseConf | Select-Object ConfigName,P12Key,ClientSecrets,AppEmail,AdminEmail,CustomerId,Domain,Preference | ConvertTo-Json -Depth 5 -Compress -Verbose:$false | Set-Content -Path $Path -Verbose:$false + $baseConf | Select-Object ConfigName,P12Key,ClientSecrets,ClientSecretOAuthScopes,AppEmail,AdminEmail,CustomerId,Domain,Preference | ConvertTo-Json -Depth 5 -Compress -Verbose:$false | Set-Content -Path $Path -Verbose:$false } catch { $PSCmdlet.ThrowTerminatingError($_) diff --git a/PSGSuite/Public/Configuration/Set-PSGSuiteConfig.ps1 b/PSGSuite/Public/Configuration/Set-PSGSuiteConfig.ps1 index 953520bc..a53e620c 100644 --- a/PSGSuite/Public/Configuration/Set-PSGSuiteConfig.ps1 +++ b/PSGSuite/Public/Configuration/Set-PSGSuiteConfig.ps1 @@ -30,6 +30,18 @@ function Set-PSGSuiteConfig { .PARAMETER ClientSecrets The string contents of the Client Secrets JSON file downloaded from the Google Developer's Console. Using the ClientSecrets JSON will prompt the user to complete OAuth2 authentication in their browser on the first run and store the retrieved Refresh and Access tokens in the user's home directory. If JSONServiceAccountKeyPath or P12KeyPath is also specified, ClientSecrets will be ignored. + .PARAMETER ClientSecretOAuthScopes + The list of OAuth scopes that are requested by default when Client Secrets authentication is used. If no OAuth scopes are specified or a command requires an OAuth scope that is not included in this list, PSGSuite will fallback to requesting the missing OAuth scopes when they are used. + + Accepted values are: + * OAuth scope - The value of a specific OAuth scope. eg 'https://www.googleapis.com/auth/admin.directory.user' + * PSGSuite function - The name of a PSGSuite function. eg 'Get-GSUser' + * API service - The service string for a specific Google API. eg 'Google.Apis.Admin.Directory.directory_v1.DirectoryService' + + When an API service or PSGSuite function is specified the values will be resolved to their respective OAuth scopes. + + See PSGSuite help or use `Get-PSGSuiteOAuthScope` to see the list of OAuth scopes, functions and API services. + .PARAMETER AppEmail The application email from the Google Developer's Console. This typically looks like the following: @@ -78,6 +90,11 @@ function Set-PSGSuiteConfig { Set-PSGSuiteConfig -ConfigName "personal" -P12KeyPath C:\Keys\PersonalKey.p12 -AppEmail "myProjectName@myProject.iam.gserviceaccount.com" -AdminEmail "admin@domain.com" -CustomerID "C83030001" -Domain "domain.com" -Preference CustomerID -ServiceAccountClientID 1175798883298324983498 -SetAsDefaultConfig This builds a config names "personal" and sets it as the default config + + .EXAMPLE + Set-PSGSuiteConfig -ConfigName "personal" -ClientSecretsPath "C:\Keys\Client_Secret.json" -ClientSecretOAuthScopes @("Get-GSUser", "Google.Apis.Calendar.v3.CalendarService") -AdminEmail "user@domain.com" -SetAsDefaultConfig + + This builds a config named "personal" and sets it as the default config. Client Secrets authentication is used and the OAuth scopes used by the "Get-GSUser" function and the Google Calendar API service are specified for use during OAuth authentication. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingConvertToSecureStringWithPlainText", "")] [cmdletbinding()] @@ -119,6 +136,10 @@ function Set-PSGSuiteConfig { [string] $ClientSecrets, [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] + [ValidateSet([PSGSuiteValidClientSecretOAuthScopeValues])] + [string[]] + $ClientSecretOAuthScopes, + [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [string] $AppEmail, [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] @@ -167,7 +188,7 @@ function Set-PSGSuiteConfig { } } Write-Verbose "Setting config name '$ConfigName'" - $configParams = @('P12Key','P12KeyPath','P12KeyPassword','JSONServiceAccountKeyPath','JSONServiceAccountKey','ClientSecretsPath','ClientSecrets','AppEmail','AdminEmail','CustomerID','Domain','Preference','ServiceAccountClientID','Webhook','Space') + $configParams = @('P12Key','P12KeyPath','P12KeyPassword','JSONServiceAccountKeyPath','JSONServiceAccountKey','ClientSecretsPath','ClientSecrets','ClientSecretOAuthScopes','AppEmail','AdminEmail','CustomerID','Domain','Preference','ServiceAccountClientID','Webhook','Space') if ($SetAsDefaultConfig -or !$configHash["DefaultConfig"]) { $configHash["DefaultConfig"] = $ConfigName } @@ -205,6 +226,12 @@ function Set-PSGSuiteConfig { $configHash["$ConfigName"]['ClientSecrets'] = (Invoke-GSEncrypt $(Get-Content $PSBoundParameters[$key] -Raw)) } } + ClientSecretOAuthScopes { + $configHash["$ConfigName"]['ClientSecretOAuthScopes'] = @() + foreach ($Entry in $PSBoundParameters[$key]){ + $configHash["$ConfigName"]['ClientSecretOAuthScopes'] += Invoke-GSEncrypt $Entry + } + } Webhook { if ($configHash["$ConfigName"].Keys -notcontains 'Chat') { $configHash["$ConfigName"]['Chat'] = @{ From 4df7cee4685a118b1f4e54a8a44ec48ab5838d0b Mon Sep 17 00:00:00 2001 From: Foggy2 <23325075+Foggy2@users.noreply.github.com> Date: Sun, 20 Jul 2025 15:15:15 +0930 Subject: [PATCH 12/13] Refactored Client Secrets authentication adding support for all OAuth scopes --- CHANGELOG.md | 61 +- PSGSuite/Aliases/PSGSuite.Aliases.ps1 | 1 - .../Class/PSGSuiteValidFunctionValues.ps1 | 10 +- .../Class/PSGSuiteValidOAuthScopeValues.ps1 | 6 +- ...=> PSGSuiteValidScopeIdentifierValues.ps1} | 16 +- PSGSuite/Class/PSGSuiteValidServiceValues.ps1 | 2 +- PSGSuite/Module/Aliases.ps1 | 1 - PSGSuite/Module/Initialization.ps1 | 5 +- PSGSuite/Module/OAuthScopes.ps1 | 48 +- .../New-GoogleServiceAccountCredential.ps1 | 95 +++ .../New-GoogleUserCredential.ps1 | 556 ++++++++++++++++++ PSGSuite/Private/EncryptionHelpers.ps1 | 6 +- .../Public/Authentication/Get-GSScope.ps1 | 92 +++ .../Get-PSGSuiteAuthenticationMethod.ps1 | 46 ++ ...teOAuthScope.ps1 => Get-PSGSuiteScope.ps1} | 44 +- .../Public/Authentication/Grant-GSScope.ps1 | 109 ++++ .../Authentication/New-GoogleService.ps1 | 103 +--- .../Authentication/Resolve-PSGSuiteScope.ps1 | 79 +++ .../Public/Authentication/Revoke-GSScope.ps1 | 131 +++++ .../Public/Authentication/Revoke-GSToken.ps1 | 91 +++ .../Configuration/Export-PSGSuiteConfig.ps1 | 2 +- .../Configuration/Set-PSGSuiteConfig.ps1 | 18 +- ci/templates/OAuthScopes.ps1 | 25 +- 23 files changed, 1388 insertions(+), 159 deletions(-) rename PSGSuite/Class/{PSGSuiteValidClientSecretOAuthScopeValues.ps1 => PSGSuiteValidScopeIdentifierValues.ps1} (95%) create mode 100644 PSGSuite/Private/Authentication/New-GoogleServiceAccountCredential.ps1 create mode 100644 PSGSuite/Private/Authentication/New-GoogleUserCredential.ps1 create mode 100644 PSGSuite/Public/Authentication/Get-GSScope.ps1 create mode 100644 PSGSuite/Public/Authentication/Get-PSGSuiteAuthenticationMethod.ps1 rename PSGSuite/Public/Authentication/{Get-PSGSuiteOAuthScope.ps1 => Get-PSGSuiteScope.ps1} (68%) create mode 100644 PSGSuite/Public/Authentication/Grant-GSScope.ps1 create mode 100644 PSGSuite/Public/Authentication/Resolve-PSGSuiteScope.ps1 create mode 100644 PSGSuite/Public/Authentication/Revoke-GSScope.ps1 create mode 100644 PSGSuite/Public/Authentication/Revoke-GSToken.ps1 diff --git a/CHANGELOG.md b/CHANGELOG.md index cf997508..828e298f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -117,6 +117,29 @@ ## 3.x.x - 2025-xx-xx +The way PSGSuite handles Client Secrets authentication has been significantly updated, requiring some changes to your setup. Service Account (JSON or P12 key) authentication is not affected by these changes. + +### Breaking Changes + +- Refactored Client Secrets Authentication + - Flexible OAuth Scope Management: + - The previous static list of OAuth Scopes is replaced by the new `ClientSecretScopes` configuration property. This property allows you to define a custom list of OAuth Scopes that are included in all authorization requests by default. + - If a required OAuth scope is missing from `ClientSecretScopes`, PSGSuite will now incrementally request authorization for each additional scope as needed. This change enables all PSGSuite functions to be used with Client Secrets authentication. + - **Action Required:** Update your Cloud Projects to include all OAuth Scopes you intend to use. To avoid individual authorization prompts for new scopes, add all necessary scopes to the `ClientSecretScopes` configuration property. + - Per-Configuration OAuth Token Tracking: + - Saved OAuth tokens are now tracked per PSGSuite configuration. This means you can use the same user account with multiple PSGSuite configurations, each with different authorized OAuth scopes. + - **Action Required**: All existing OAuth tokens must be re-authorized. After updating, you'll be prompted to re-authorize PSGSuite to access your user account. + - Updated OAuth Authorization Workflow: + - PSGSuite now exclusively uses `LocalServerCodeReceiver` for user authorization prompts. This replaces the deprecated `PromptCodeReceiver`. + - **Requirement:** A web browser must be installed on the local system for this method to work. + - Enhanced Authorization Token Validation: + - Authorization tokens are now validated to ensure they were issued for the user who initiated the authorization. This prevents commands from being executed against the wrong user account. + - If a token is issued for an incorrect user, it will be revoked, and the command will fail. Previously, the token would be accepted regardless of the linked user. + - To enable this validation, PSGSuite will always request the `https://www.googleapis.com/auth/userinfo.email` OAuth Scope when authorizing user accounts, regardless of the `ClientSecretScopes` configuration. + - **Action Required:** Update all Cloud Projects to include the `https://www.googleapis.com/auth/userinfo.email` scope. + +### Other Changes + - Added `-lib` parameter to `Import-GoogleSDK` that defines the path of the directory containing the Google API libraries. - Added functionality to programmatically generate module components during the module build process. Further details can be found at [ci\templates\README.md](ci\templates\README.md). - Added two additional tasks `Download` and `Generate` to the module build process. @@ -129,20 +152,30 @@ - Added `-DebugBuild` switch to `build.ps1` for improved module debugging. When built with this switch the compiled `PSGSuite.psm1` file will: - Link directly to each source code file found in the `PSGSuite` directory. - Export all module functions and variables to the PowerShell session. -- Added `Get-PSGSuiteOAuthScope` function that returns the OAuth scopes that are currently used by PSGSuite. -- Added `Get-PSGSuiteScope` alias to the `Get-PSGSuiteOAuthScope` function. -- Added `PSGSuiteValidServiceValues` class that contains and validates the Google API service names that are used by PSGSuite. eg, `Google.Apis.Slides.v1.SlidesService` -- Added `PSGSuiteValidFunctionValues` class that contains and validates the list of public PSGSuite function names. eg, `Get-GSPresentation` -- Added `PSGSuiteValidOAuthScopeValues` class that contains and validates the list of OAuth scopes that are used by PSGSuite. eg, `https://www.googleapis.com/auth/drive` -- Added `PSGSuiteValidClientSecretOAuthScopeValues` class that contains and validates all valid values from `PSGSuityeValidServiceValues`, `PSGSuiteValidFunctionValues` & `PSGSuiteValidOAuthScopeValues`. -- Added `ci\templates\OAuthScopes.ps1` generation template that scans the PSGSuite source directory for the OAuth scopes, function names and Google API service names that are used by PSGSuite. The discovered data is then used to programmatically produce the following items: - - `Module\OAuthScopes.ps1` - Contains the module variable `$script:_PSGSuiteOAuthScopes` that contains the dataset used by `Get-PSGSuiteOAuthScope` - - `Class\PSGSuiteValidServiceValues.ps1` - - `Class\PSGSuiteValidFunctionValues.ps1` - - `Class\PSGSuiteValidOAuthScopeValues.ps1` - - `Class\PSGSuiteValidClientSecretOAuthScopeValues.ps1` -- Added `ClientSecretOAuthScopes` property to the module configuration schema. This property defines the list of OAuth scopes that are requested by default when Client Secrets authentication is used. If no OAuth scopes are specified or a command requires an OAuth scope that is not included in this list, PSGSuite will fallback to requesting each additional OAuth scope when they are used. -- Added `-ClientSecretOAuthScopes` parameter to `Set-PSGSuiteConfig`. This parameter is validated and accepts any valid Function, API service, or OAuth scope that is used by PSGSuite. eg, `Get-GSPresentation`, `https://www.googleapis.com/auth/drive` or `Google.Apis.Slides.v1.SlidesService`. +- Added new functions: + * `Get-PSGSuiteScope`: Returns the OAuth scopes used by PSGSuite. + * `Get-GSScope`: Returns the OAuth scopes PSGSuite is authorized to access for a specified user when Client Secrets authentication is used. + * `Get-PSGSuiteAuthenticationMethod`: Returns the name of the currently configured authentication method (e.g., `Service-Account-JSON-Key`, `Service-Account-P12-Key`, `Client-Secrets-OAuth`). + - `Grant-GSScope`: Supports `Client Secrets` authentication only. Requests authorization for PSGSuite to access the specified OAuth scopes. + - `Revoke-GSScope`: Supports `Client Secrets` authentication only. Revokes PSGSuite's authorization to access the specified OAuth scopes. + - `Revoke-GSToken`: Supports `Client Secrets` authentication only. Revokes PSGSuite's authorization to access the specified user account. + - `Resolve-PSGSuiteScope`: Resolves the provided OAuth scope identifiers to their corresponding OAuth scope values. +- Added new parameter validation classes: + * `PSGSuiteValidServiceValues`: Validates Google API service names (e.g., `Google.Apis.Slides.v1.SlidesService`). + * `PSGSuiteValidFunctionValues`: Validates public PSGSuite function names (e.g., `Get-GSPresentation`). + * `PSGSuiteValidScopeValues`: Validates OAuth scopes used by PSGSuite (e.g., `https://www.googleapis.com/auth/drive`). + * `PSGSuiteValidScopeIdentifierValues`: Validates all values from `PSGSuiteValidServiceValues`, `PSGSuiteValidFunctionValues`, and `PSGSuiteValidScopeValues`. +- Added dynamic content generation templates: +* Added `ClientSecretScopes` property to the module configuration schema. This property defines the default OAuth scopes requested during Client Secrets authentication. +* Updated `Set-PSGSuiteConfig`, `Export-PSGSuiteConfig` and `Get-GSDecryptedConfig` with support for the `ClientSecretScopes` configuration property. +* Refactored `New-GoogleService` moving the existing authentication code into new private functions `New-ServiceAccountCredential` and `New-GoogleUserCredential` for improved code re-usability. +* Added template `ci\templates\OAuthScopes.ps1` that scans the PSGSuite source directory to automatically generate: + * `Module\OAuthScopes.ps1` (containing `$script:_PSGSuiteScopes` used by `Get-PSGSuiteScope`) + * `Class\PSGSuiteValidServiceValues.ps1` + * `Class\PSGSuiteValidFunctionValues.ps1` + * `Class\PSGSuiteValidOAuthScopeValues.ps1` + * `Class\PSGSuiteValidScopeIdentifierValues.ps1` + ## 3.0.0 - 2024-11-20 diff --git a/PSGSuite/Aliases/PSGSuite.Aliases.ps1 b/PSGSuite/Aliases/PSGSuite.Aliases.ps1 index d36c904a..ec8471ac 100644 --- a/PSGSuite/Aliases/PSGSuite.Aliases.ps1 +++ b/PSGSuite/Aliases/PSGSuite.Aliases.ps1 @@ -42,5 +42,4 @@ 'Update-GSGmailSendAsSettings' = 'Update-GSGmailSendAsAlias' 'Update-GSSheetValue' = 'Export-GSSheet' 'Update-GSTeamDrive' = 'Update-GSDrive' - 'Get-PSGSuiteScope' = 'Get-PSGSuiteOAuthScope' } diff --git a/PSGSuite/Class/PSGSuiteValidFunctionValues.ps1 b/PSGSuite/Class/PSGSuiteValidFunctionValues.ps1 index 10a5f252..10f477a3 100644 --- a/PSGSuite/Class/PSGSuiteValidFunctionValues.ps1 +++ b/PSGSuite/Class/PSGSuiteValidFunctionValues.ps1 @@ -1,4 +1,4 @@ -# Programmatically generated from template 'OAuthScopes.ps1' +# Programmatically generated from template 'oauthscopes.ps1' # This file will be overwritten during the module build process. # Class that provides parameter validation for the names of the public PSGSuite functions. @@ -97,6 +97,7 @@ class PSGSuiteValidFunctionValues : System.Management.Automation.IValidateSetVal 'Get-GSOrganizationalUnit', 'Get-GSPresentation', 'Get-GSResource', + 'Get-GSScope', 'Get-GSSheetInfo', 'Get-GSStudentGuardian', 'Get-GSStudentGuardianInvitation', @@ -109,9 +110,11 @@ class PSGSuiteValidFunctionValues : System.Management.Automation.IValidateSetVal 'Get-GSUserSchema', 'Get-GSUserToken', 'Get-GSUserVerificationCodes', + 'Get-PSGSuiteAuthenticationMethod', 'Get-PSGSuiteConfig', - 'Get-PSGSuiteOAuthScope', + 'Get-PSGSuiteScope', 'Get-PSGSuiteServiceCache', + 'Grant-GSScope', 'Hide-GSDrive', 'Import-GSSheet', 'Import-PSGSuiteConfig', @@ -176,8 +179,11 @@ class PSGSuiteValidFunctionValues : System.Management.Automation.IValidateSetVal 'Remove-GSUserPhoto', 'Remove-GSUserSchema', 'Remove-GSUserToken', + 'Resolve-PSGSuiteScope', 'Restore-GSUser', + 'Revoke-GSScope', 'Revoke-GSStudentGuardianInvitation', + 'Revoke-GSToken', 'Revoke-GSUserVerificationCodes', 'Send-GSChatMessage', 'Send-GSGmailSendAsConfirmation', diff --git a/PSGSuite/Class/PSGSuiteValidOAuthScopeValues.ps1 b/PSGSuite/Class/PSGSuiteValidOAuthScopeValues.ps1 index aff5c7d0..fab11eec 100644 --- a/PSGSuite/Class/PSGSuiteValidOAuthScopeValues.ps1 +++ b/PSGSuite/Class/PSGSuiteValidOAuthScopeValues.ps1 @@ -1,4 +1,4 @@ -# Programmatically generated from template 'OAuthScopes.ps1' +# Programmatically generated from template 'oauthscopes.ps1' # This file will be overwritten during the module build process. # Class that provides parameter validation for the list of OAuth scopes that are used by all PSGSuite functions. @@ -32,7 +32,9 @@ class PSGSuiteValidOAuthScopeValues : System.Management.Automation.IValidateSetV 'https://www.googleapis.com/auth/classroom.rosters', 'https://www.googleapis.com/auth/drive', 'https://www.googleapis.com/auth/gmail.settings.basic', - 'https://www.googleapis.com/auth/gmail.settings.sharing' + 'https://www.googleapis.com/auth/gmail.settings.sharing', + 'https://www.googleapis.com/auth/userinfo.email', + 'openid' ) return $Values } diff --git a/PSGSuite/Class/PSGSuiteValidClientSecretOAuthScopeValues.ps1 b/PSGSuite/Class/PSGSuiteValidScopeIdentifierValues.ps1 similarity index 95% rename from PSGSuite/Class/PSGSuiteValidClientSecretOAuthScopeValues.ps1 rename to PSGSuite/Class/PSGSuiteValidScopeIdentifierValues.ps1 index 8ae15552..64f5f439 100644 --- a/PSGSuite/Class/PSGSuiteValidClientSecretOAuthScopeValues.ps1 +++ b/PSGSuite/Class/PSGSuiteValidScopeIdentifierValues.ps1 @@ -1,7 +1,7 @@ -# Programmatically generated from template 'OAuthScopes.ps1' +# Programmatically generated from template 'oauthscopes.ps1' # This file will be overwritten during the module build process. -class PSGSuiteValidClientSecretOAuthScopeValues : System.Management.Automation.IValidateSetValuesGenerator { +class PSGSuiteValidScopeIdentifierValues : System.Management.Automation.IValidateSetValuesGenerator { [string[]] GetValidValues() { $Values = @( 'Google.Apis.Admin.DataTransfer.datatransfer_v1.DataTransferService', @@ -108,6 +108,7 @@ class PSGSuiteValidClientSecretOAuthScopeValues : System.Management.Automation.I 'Get-GSOrganizationalUnit', 'Get-GSPresentation', 'Get-GSResource', + 'Get-GSScope', 'Get-GSSheetInfo', 'Get-GSStudentGuardian', 'Get-GSStudentGuardianInvitation', @@ -120,9 +121,11 @@ class PSGSuiteValidClientSecretOAuthScopeValues : System.Management.Automation.I 'Get-GSUserSchema', 'Get-GSUserToken', 'Get-GSUserVerificationCodes', + 'Get-PSGSuiteAuthenticationMethod', 'Get-PSGSuiteConfig', - 'Get-PSGSuiteOAuthScope', + 'Get-PSGSuiteScope', 'Get-PSGSuiteServiceCache', + 'Grant-GSScope', 'Hide-GSDrive', 'Import-GSSheet', 'Import-PSGSuiteConfig', @@ -187,8 +190,11 @@ class PSGSuiteValidClientSecretOAuthScopeValues : System.Management.Automation.I 'Remove-GSUserPhoto', 'Remove-GSUserSchema', 'Remove-GSUserToken', + 'Resolve-PSGSuiteScope', 'Restore-GSUser', + 'Revoke-GSScope', 'Revoke-GSStudentGuardianInvitation', + 'Revoke-GSToken', 'Revoke-GSUserVerificationCodes', 'Send-GSChatMessage', 'Send-GSGmailSendAsConfirmation', @@ -262,7 +268,9 @@ class PSGSuiteValidClientSecretOAuthScopeValues : System.Management.Automation.I 'https://www.googleapis.com/auth/classroom.rosters', 'https://www.googleapis.com/auth/drive', 'https://www.googleapis.com/auth/gmail.settings.basic', - 'https://www.googleapis.com/auth/gmail.settings.sharing' + 'https://www.googleapis.com/auth/gmail.settings.sharing', + 'https://www.googleapis.com/auth/userinfo.email', + 'openid' ) return $Values } diff --git a/PSGSuite/Class/PSGSuiteValidServiceValues.ps1 b/PSGSuite/Class/PSGSuiteValidServiceValues.ps1 index 14f5c289..9564350d 100644 --- a/PSGSuite/Class/PSGSuiteValidServiceValues.ps1 +++ b/PSGSuite/Class/PSGSuiteValidServiceValues.ps1 @@ -1,4 +1,4 @@ -# Programmatically generated from template 'OAuthScopes.ps1' +# Programmatically generated from template 'oauthscopes.ps1' # This file will be overwritten during the module build process. # Class that provides parameter validation for the Google API services that are used by PSGSuite. diff --git a/PSGSuite/Module/Aliases.ps1 b/PSGSuite/Module/Aliases.ps1 index 7b355218..1329ba39 100644 --- a/PSGSuite/Module/Aliases.ps1 +++ b/PSGSuite/Module/Aliases.ps1 @@ -45,7 +45,6 @@ $aliasHash = # Alias => => => => => => => => Function 'Update-GSGmailSendAsSettings' = 'Update-GSGmailSendAsAlias' 'Update-GSSheetValue' = 'Export-GSSheet' 'Update-GSTeamDrive' = 'Update-GSDrive' - 'Get-PSGSuiteScope' = 'Get-PSGSuiteOAuthScope' } foreach ($key in $aliasHash.Keys) { diff --git a/PSGSuite/Module/Initialization.ps1 b/PSGSuite/Module/Initialization.ps1 index fa81a090..73c5d86b 100644 --- a/PSGSuite/Module/Initialization.ps1 +++ b/PSGSuite/Module/Initialization.ps1 @@ -107,4 +107,7 @@ Get-PSGSuiteConfig -Path '$ModuleRoot\$env:USERNAME-$env:COMPUTERNAME-$env:PSGSu } catch { Write-Warning "There was no config returned! Please make sure you are using the correct key or have a configuration already saved." -} \ No newline at end of file +} + +# File path to the directory where Google OAuth tokens are persisted on disk +$Script:_PSGSuiteCredPath = Join-Path (Resolve-Path (Join-Path "~" ".scrthq")) "PSGSuite" \ No newline at end of file diff --git a/PSGSuite/Module/OAuthScopes.ps1 b/PSGSuite/Module/OAuthScopes.ps1 index a92e0566..f949aa87 100644 --- a/PSGSuite/Module/OAuthScopes.ps1 +++ b/PSGSuite/Module/OAuthScopes.ps1 @@ -1,9 +1,19 @@ -# Programmatically generated from template 'OAuthScopes.ps1' +# Programmatically generated from template 'oauthscopes.ps1' # This file will be overwritten during the module build process. -# Scope data that is used by the Get-PSGSuiteOAuthScope function. -$script:_PSGSuiteOAuthScopes = @' +# Scope data that is used by the Get-PSGSuiteScope function. +$script:_PSGSuiteScopes = @' [ + { + "Function": null, + "Service": null, + "Scope": "https://www.googleapis.com/auth/userinfo.email" + }, + { + "Function": null, + "Service": null, + "Scope": "openid" + }, { "Function": "Add-GSCalendarEventReminder", "Service": null, @@ -144,13 +154,23 @@ $script:_PSGSuiteOAuthScopes = @' "Service": null, "Scope": null }, + { + "Function": "Get-GSScope", + "Service": null, + "Scope": null + }, + { + "Function": "Get-PSGSuiteAuthenticationMethod", + "Service": null, + "Scope": null + }, { "Function": "Get-PSGSuiteConfig", "Service": null, "Scope": null }, { - "Function": "Get-PSGSuiteOAuthScope", + "Function": "Get-PSGSuiteScope", "Service": null, "Scope": null }, @@ -159,6 +179,11 @@ $script:_PSGSuiteOAuthScopes = @' "Service": null, "Scope": null }, + { + "Function": "Grant-GSScope", + "Service": null, + "Scope": null + }, { "Function": "Import-PSGSuiteConfig", "Service": null, @@ -174,6 +199,21 @@ $script:_PSGSuiteOAuthScopes = @' "Service": null, "Scope": null }, + { + "Function": "Resolve-PSGSuiteScope", + "Service": null, + "Scope": null + }, + { + "Function": "Revoke-GSScope", + "Service": null, + "Scope": null + }, + { + "Function": "Revoke-GSToken", + "Service": null, + "Scope": null + }, { "Function": "Set-PSGSuiteConfig", "Service": null, diff --git a/PSGSuite/Private/Authentication/New-GoogleServiceAccountCredential.ps1 b/PSGSuite/Private/Authentication/New-GoogleServiceAccountCredential.ps1 new file mode 100644 index 00000000..5171b6e2 --- /dev/null +++ b/PSGSuite/Private/Authentication/New-GoogleServiceAccountCredential.ps1 @@ -0,0 +1,95 @@ +function New-GoogleServiceAccountCredential { + <# + .SYNOPSIS + Creates a new Google ServiceAccountCredential object for use with the Service-Account-JSON-Key and Service-Account-P12-Key authentication methods. The credential object is used to authenticate PSGSuite to Google. + + .DESCRIPTION + Creates a new Google ServiceAccountCredential object for use with the Service-Account-JSON-Key and Service-Account-P12-Key authentication methods. The credential object is used to authenticate PSGSuite to Google. + + .PARAMETER Scope + The scope or scopes that will be accessed with the ServiceAccountCredential, e.g. https://www.googleapis.com/auth/admin.reports.audit.readonly + + .PARAMETER User + The user that is being impersonated by the service account. + + .EXAMPLE + $CredentialParams = @{ + Scope = 'https://www.googleapis.com/auth/admin.reports.audit.readonly' + User = 'user@email.com' + } + $Credential = New-GoogleServiceAccountCredential @CredentialParams + + #> + [OutputType('Google.Apis.Auth.OAuth2.ServiceAccountCredential')] + [CmdletBinding()] + Param( + [Parameter(Mandatory = $true,Position = 0)] + [ValidateNotNullOrEmpty()] + [String[]] + $Scope, + [Parameter(Mandatory = $true,Position = 1)] + [String] + $User + ) + + + $AuthMethod = Get-PSGSuiteAuthenticationMethod + Switch ($AuthMethod){ + + 'Service-Account-JSON-Key' { + Write-Verbose "Building ServiceAccountCredential from JSONServiceAccountKey as user '$User'" + try { + if (-not $script:PSGSuite.JSONServiceAccountKey) { + $script:PSGSuite.JSONServiceAccountKey = ([System.IO.File]::ReadAllBytes($script:PSGSuite.JSONServiceAccountKeyPath)) + Set-PSGSuiteConfig -ConfigName $script:PSGSuite.ConfigName -JSONServiceAccountKey $script:PSGSuite.JSONServiceAccountKey -Verbose:$false + } + $stream = New-Object System.IO.MemoryStream $([System.Text.Encoding]::ASCII.GetBytes($script:PSGSuite.JSONServiceAccountKey)), $null + ([Google.Apis.Auth.OAuth2.GoogleCredential]::FromStream($stream)).CreateWithUser($User).CreateScoped($Scope).UnderlyingCredential + } + catch { + $PSCmdlet.ThrowTerminatingError($_) + } + finally { + if ($stream) { + $stream.Close() + } + } + } + + 'Service-Account-P12-Key' { + try { + Write-Verbose "Building ServiceAccountCredential from P12Key as user '$User'" + if ($script:PSGSuite.P12KeyPath -or $script:PSGSuite.P12Key) { + 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:PSGSuite.P12KeyPassword) { + $P12KeyPassword = $script:PSGSuite.P12KeyPassword + } + else { + $P12KeyPassword = "notasecret" + } + $certificate = New-Object 'System.Security.Cryptography.X509Certificates.X509Certificate2' -ArgumentList ([System.Byte[]]$script:PSGSuite.P12Key),$P12KeyPassword,([System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable) + } + else { + $certificate = $script:PSGSuite.P12KeyObject + } + 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) + + } + catch { + $PSCmdlet.ThrowTerminatingError($_) + } + } + + Default { + $PSCmdlet.ThrowTerminatingError((ThrowTerm "Unable to create ServiceAccountCredential. PSGSuite is not currently configured for Service-Account-JSON-Key or Service-Account-P12-Key authentication.")) + } + } + +} \ No newline at end of file diff --git a/PSGSuite/Private/Authentication/New-GoogleUserCredential.ps1 b/PSGSuite/Private/Authentication/New-GoogleUserCredential.ps1 new file mode 100644 index 00000000..a3242acc --- /dev/null +++ b/PSGSuite/Private/Authentication/New-GoogleUserCredential.ps1 @@ -0,0 +1,556 @@ +function New-GoogleUserCredential { + <# + .SYNOPSIS + Creates a new Google UserCredential object for use with the Client-Secrets-OAuth authentication method. The credential object is used to authenticate PSGSuite to Google. + + .DESCRIPTION + Creates a new Google UserCredential object for use with the Client-Secrets-OAuth authentication method. The credential object is used to authenticate PSGSuite to Google. + + The Client-Secrets-OAuth authentication method requires interactive input from an end-user whenever the authorised OAuth scopes are changing. See the Notes section for further details. + + By default the generated UserCredential will include all OAuth scopes from the following sources: + - `-scopes` parameter + - All supplemental OAuth scopes with an existing authorisation + - All default OAuth scopes + + .NOTES + UserCredentials are only valid for the Client-Secrets-OAuth authentication method. + + An interactive authorisation workflow is used to delegate user credentials to PSGSuite. When required, the system web browser will be automatically launched and the user will be prompted to complete the Google OAuth authorisation workflow. Commands will be unable to be processed until OAuth authorisation has been completed. + + It is expected that the interactive authorisation workflow will only be required when: + - It is the first time an authorisation token is being requested for a given user. + - Additional OAuth scopes are being authorised. + - Authorisation is being revoked for specific OAuth scopes. + - The existing OAuth authorisation token is expired and cannot be renewed. + + The default OAuth scopes are configured via the PSGSuite `ClientSecretsScopes` configuration parameter. See `Set-PSGSuiteConfig` for further details. + + A supplemental OAuth scope is any OAuth scope that is not configured as a default OAuth scope. + + .PARAMETER Scope + The OAuth scope or scopes that will be authorised and added to the UserCredential, e.g. https://www.googleapis.com/auth/admin.reports.audit.readonly + + It is possible for a user to decline an OAuth scope during the interactive authorisation workflow. If any of the OAuth scopes specified with the `-scope` parameter are declined a terminating error will be thrown. + + By dafault all authorised supplemental OAuth scopes and all default OAuth scopes will be included even if they were not provided to the `-scope` parameter. + + .PARAMETER User + The user to be authenticated. The generated UserCredential will be validated against this user. If a different user account is attached to the credential a terminating error will be thrown and the credential will be revoked. Validation of the user can be skipped by specifying the `-SkipUserValidation` switch. + + .PARAMETER ExcludeScope + Exclues and revokes authorisation for the provided supplemental OAuth scopes that were not included with the `-scope` parameter. + + .PARAMETER ExcludeSupplementalScopes + Excludes and revokes authorisation for all supplemental OAuth scopes that were not included with the `-scope` parameter. + + .PARAMETER SkipUserValidation + Skips validation of the user account attached to the UserCredential. + + .PARAMETER Offline + The UserCredential will be generated in offline mode without prompting the user to complete the interactive authorisation workflow. This is only possible when a previously auithorised UserCrednetial exists in the disk cache. + + All OAuth scopes from the cached UserCredential with an existing authorisation will be added to the new UserCredential. If any default OAuth scopes do not have an existing authorisation they will be excluded from the new UserCredential. + + .EXAMPLE + This will return a UserCredential for 'user@email.com' that includes the following OAuth scopes: + - https://www.googleapis.com/auth/admin.reports.audit.readonly + - All default OAuth scopes + - All authorised supplemental OAuth scopes + + Interactive authorisation will be prompted if any of the following is true: + - OAuth scope 'https://www.googleapis.com/auth/admin.reports.audit.readonly' does not have an existing authorisation + - Any of the default OAuth scopes do not have an existing authorisation + + $CredentialParams = @{ + Scope = 'https://www.googleapis.com/auth/admin.reports.audit.readonly' + User = 'user@email.com' + } + $Credential = New-GoogleUserCredential @CredentialParams + + .EXAMPLE + This will return a UserCredential for 'user@email.com' that includes the following OAuth scopes: + - https://www.googleapis.com/auth/admin.reports.audit.readonly + - All default OAuth scopes + + All supplemental OAuth scopes will be excluded except for 'https://www.googleapis.com/auth/admin.reports.audit.readonly'. + + Interactive authorisation will be prompted if any of the following is true: + - OAuth scope 'https://www.googleapis.com/auth/admin.reports.audit.readonly' does not have an existing authorisation + - Any of the default OAuth scopes do not have an existing authorisation + - Any authorised supplemental OAuth scopes have been excluded + + $CredentialParams = @{ + Scope = 'https://www.googleapis.com/auth/admin.reports.audit.readonly' + User = 'user@email.com' + } + $Credential = New-GoogleUserCredential @CredentialParams -ExcludeSupplementalScopes + + .EXAMPLE + This will return a UserCredential for 'user@email.com' that includes the following OAuth scopes: + - https://www.googleapis.com/auth/admin.reports.audit.readonly + - All default OAuth scopes + - All authorised supplemental OAuth scopes except 'https://www.googleapis.com/auth/calendar' + + Interactive authorisation will be prompted if any of the following is true: + - OAuth scope 'https://www.googleapis.com/auth/admin.reports.audit.readonly' does not have an existing authorisation + - Any of the default OAuth scopes do not have an existing authorisation + - Supplemental OAuth scope 'https://www.googleapis.com/auth/calendar' has an existing authorisation + + $CredentialParams = @{ + Scope = 'https://www.googleapis.com/auth/admin.reports.audit.readonly' + User = 'user@email.com' + ExcludeScope = 'https://www.googleapis.com/auth/calendar' + } + $Credential = New-GoogleUserCredential @CredentialParams + + .EXAMPLE + This will return a UserCredential for 'user@email.com' that includes the following OAuth scopes: + - All OAuth scopes with an existing authorisation + + Interactive authorisation will not be prompted. + + A UserCredential will only be returned if an existing UserCredential is found in the disk cache. + + $CredentialParams = @{ + User = 'user@email.com' + } + $Credential = New-GoogleUserCredential @CredentialParams -Offline + + #> + [OutputType('Google.Apis.Auth.OAuth2.UserCredential')] + [CmdletBinding(SupportsShouldProcess, ConfirmImpact='High')] + Param( + [Parameter(Mandatory = $true, ParameterSetName = "grant")] + [String[]] + $Scope, + [Parameter(Mandatory = $true, ParameterSetName = "revoke")] + [String[]] + $ExcludeScope, + [Parameter(Mandatory = $true, ParameterSetName = "grant")] + [Parameter(Mandatory = $true, ParameterSetName = "revoke")] + [Parameter(Mandatory = $true, ParameterSetName = "Offline")] + [Parameter(Mandatory = $true, ParameterSetName = "grantDefault")] + [String] + $User, + [Parameter(Mandatory = $false, ParameterSetName = "grant")] + [Parameter(Mandatory = $false, ParameterSetName = "grantDefault")] + [Switch] + $ExcludeSupplementalScopes, + [Parameter(Mandatory = $false, ParameterSetName = "grant")] + [Parameter(Mandatory = $false, ParameterSetName = "revoke")] + [Parameter(Mandatory = $false, ParameterSetName = "Offline")] + [Parameter(Mandatory = $false, ParameterSetName = "grantDefault")] + [Switch] + $SkipUserValidation, + [Parameter(Mandatory = $true, ParameterSetName = "Offline")] + [Switch] + $Offline + ) + Begin { + + # Get the default OAuth scopes from the config + if (-not $script:_PSGSuiteClientSecretScopes) { + Write-Verbose 'Building the default OAuth scope cache' + # Stores the resolved list of default OAuth scopes for re-use + $script:_PSGSuiteClientSecretScopes = [System.Collections.Generic.HashSet[String]]::new([System.StringComparer]::OrdinalIgnoreCase) + Resolve-PSGSuiteScope -DefaultScopes | ForEach-Object { + $script:_PSGSuiteClientSecretScopes.add($_) | Out-Null + } + } + + # Load the client secrets file + if (-not $script:PSGSuite.ClientSecrets) { + Write-Verbose "Updating the current PSGSuite configuration with the ClientSecrets values." + $script:PSGSuite.ClientSecrets = ([System.IO.File]::ReadAllText($script:PSGSuite.ClientSecretsPath)) + Set-PSGSuiteConfig -ConfigName $script:PSGSuite.ConfigName -ClientSecrets $script:PSGSuite.ClientSecrets -Verbose:$false + } + if (-not $script:_PSGSuiteClientSecrets){ + Write-Verbose "Loading the ClientSecrets into memory." + $stream = New-Object System.IO.MemoryStream $([System.Text.Encoding]::ASCII.GetBytes(($script:PSGSuite.ClientSecrets))),$null + # Stores the loaded Client Secrets file for re-use + $script:_PSGSuiteClientSecrets = [Google.Apis.Auth.OAuth2.GoogleClientSecrets]::Load($stream).Secrets + $stream.close() + } + + # Initialize the in-memory user credential cache + if (-not $script:_PSGSuiteUserCredentials) { + # Stores the instantiated user credentials in memory for re-use. + # To avoid conflicts when tokens are automatically refreshed we will ensure that only one instance of a credential is + # instantiated and used at any given time. + $script:_PSGSuiteUserCredentials = @{} + } + + # Initialize the FileDataStore used for storing and retrieving cached OAuth tokens. + $Datastore = New-Object 'Google.Apis.Util.Store.FileDataStore' -ArgumentList $Script:_PSGSuiteCredPath,$true + + } + + Process { + + Write-Verbose "Generating UserCredential for user '$user' in '$($PSCmdlet.ParameterSetName)' mode." + + # The $TokenKey is used as the key when retrieving tokens (UserCredentials) from disk. + # We will create the key such that all tokens will be unique per PSGSuite config and user combination. Allowing the same user to have different + # tokens per PSGSuite configuration. + # https://github.com/googleapis/google-api-dotnet-client/issues/2709 + $TokenKey = @($script:PSGSuite.ConfigName, $User) -Join '-' + $TokenName = [Google.Apis.Util.Store.FileDataStore]::GenerateStoredKey($TokenKey, [Google.Apis.Auth.OAuth2.Responses.TokenResponse]) + $TokenPath = Join-Path $Datastore.FolderPath $TokenName + + + + # Build the list of scopes to request + # + # The following OAuth scopes will be included in each UserCredential/auhtorisation request: + # GRANT (Online): + # - $scope parameter + # - Default OAuth scopes + # - All authorised supplemental OAuth scopes if $ExcludeSupplementalScopes parameter is $false + # + # REVOKE (Online): + # - Default OAuth scopes + # - All authorised supplemental OAuth scopes except the scopes from the $ExcludeScope parameter + # + # OFFLINE: + # - All existing authorised OAuth scopes + # + # GrantDefault (Online): + # - Default OAuth scopes + + + # All scopes to be requested - May include: default, existing and required scopes + $ScopesToRequest = @() + # All scopes that were passed to the $scope parameter + $ScopesRequired = [System.Collections.Generic.HashSet[String]]::new([System.StringComparer]::OrdinalIgnoreCase) + # All scopes that will be revoked + $ScopesToRevoke = @() + # Existing scopes that will be kept + $ScopesToKeep = @() + # All requested scopes without an existing authorisation + $ScopesToGrant = @() + # All existing scopes for the user + $ExistingScopes = @() + # All scopes that are to be safely excluded + $ScopesToExclude = [System.Collections.Generic.HashSet[String]]::new([System.StringComparer]::OrdinalIgnoreCase) + + + + # Find the existing credential if one exists + # + # Search in memory first. Otherwise try the disk datastore. + $ExistingCredential = If ($script:_PSGSuiteUserCredentials.containsKey($TokenKey)){ + + Write-Verbose "Getting existing UserCredential '$TokenKey' from memory" + $script:_PSGSuiteUserCredentials[$TokenKey] + + } else { + + If ((Test-Path $TokenPath)){ + # Online flows - Load the existing credential + If ($PSCmdlet.ParameterSetName -ne "Offline"){ + Write-Verbose "Invoking the offline flow to get existing UserCredential '$TokenKey' from disk" + New-GoogleUserCredential -User $User -Offline + } + } else { + # Terminate Offline flow - Offline avoids prompting for user input. + # Terminate Revoke flows - Can't revoke scopes that don't exist. + If (($PSCmdlet.ParameterSetName -eq "Offline") -or ($PSCmdlet.ParameterSetName -eq "Revoke")){ + Write-Verbose "Unable to continue in '$($PSCmdlet.ParameterSetName)' mode. No existing UserCredential was found for user '$user'" + Return + } + } + + + } + + + + # Offline flow - Terminate if credential was found in memory + # If it is in memory, we will assume that it was validated already and terminate the flow here. + If ($PSCmdlet.ParameterSetName -eq "Offline"){ + If ($ExistingCredential){ + Write-Verbose "Returning existing UserCredential with $($ExistingCredential.Token.Scope.split(' ').count) OAuth scopes for user '$user'" + Return $ExistingCredential + } + } + + + + # Online Flows - Parse the existing credential + If ($PSCmdlet.ParameterSetName -ne 'Offline'){ + If ($ExistingCredential){ + Write-Verbose "The existing OAuth scopes for '$user' are:" + ForEach ($ExistingScope in $ExistingCredential.Token.Scope.split(' ')){ + $ExistingScopes += $ExistingScope + If ($script:_PSGSuiteClientSecretScopes.contains($ExistingScope)){ + Write-Verbose " $ExistingScope [default]" + } else { + Write-Verbose " $ExistingScope" + } + } + } else { + Write-Verbose "Existing UserCredential '$TokenKey' not found." + } + } + + + # Online Flows - Determine the scopes required for the UserCredential + If ($PSCmdlet.ParameterSetName -ne "Offline"){ + Switch ($PSCmdlet.ParameterSetName){ + + "Grant" { + # Required scopes + Write-Verbose "The required OAuth scopes to request are:" + ForEach ($RequiredScope in $Scope){ + If (-not $script:_PSGSuiteClientSecretScopes.contains($RequiredScope)){ + Write-Verbose " $RequiredScope" + $ScopesToRequest += $RequiredScope + $ScopesRequired.add($RequiredScope) | Out-Null + } else { + Write-Verbose " $RequiredScope" + } + } + + # Default Scopes + Write-Verbose "The default OAuth scopes to request are:" + ForEach ($DefaultScope in $script:_PSGSuiteClientSecretScopes){ + Write-Verbose " $DefaultScope" + $ScopesToRequest += $DefaultScope + } + } + + "GrantDefault" { + # Default Scopes + Write-Verbose "The required OAuth scopes to request are:" + ForEach ($DefaultScope in $script:_PSGSuiteClientSecretScopes){ + Write-Verbose " $DefaultScope" + $ScopesToRequest += $DefaultScope + } + } + + "Revoke" { + # Default Scopes + Write-Verbose "The default OAuth scopes to request are:" + ForEach ($DefaultScope in $script:_PSGSuiteClientSecretScopes){ + Write-Verbose " $DefaultScope" + $ScopesToRequest += $DefaultScope + } + + # Exclude scopes + Write-Verbose "The OAuth scopes to exclude are:" + ForEach ($RequiredScope in $ExcludeScope){ + If (-not $script:_PSGSuiteClientSecretScopes.contains($RequiredScope)){ + Write-Verbose " $RequiredScope" + $ScopesToExclude.add($RequiredScope) | Out-Null + } else { + Write-Verbose " [skipped] $RequiredScope" + } + } + } + + } + } + + + + # Online Flows - Check the scopes and determine the changes + If ($PSCmdlet.ParameterSetName -ne "Offline"){ + Write-Verbose "Checking scopes:" + Compare-Object -ReferenceObject $ExistingScopes -DifferenceObject $ScopesToRequest -IncludeEqual | ForEach-Object { + + # Mark the verbose output with the default and required status of each scope. + If ($script:_PSGSuiteClientSecretScopes.contains($_.InputObject)){ + $DefaultTag = "[default]" + } else { + $DefaultTag = "" + } + If ($ScopesRequired.contains($_.InputObject)){ + $Required = "[required]" + } else { + $Required = "" + } + + If ($_.SideIndicator -eq '=>'){ + Write-Verbose " [grant] $($_.InputObject) $Required$DefaultTag" + $ScopesToGrant += $_.InputObject + } elseif ($_.SideIndicator -eq '<=') { + If ($ExcludeSupplementalScopes){ + Write-Verbose " [revoke] $($_.InputObject) $Required$DefaultTag" + $ScopesToRevoke += $_.InputObject + } else { + If ($ScopesToExclude.Contains($_.InputObject)){ + Write-Verbose " [revoke] $($_.InputObject) $Required$DefaultTag" + $ScopesToRevoke += $_.InputObject + } else { + Write-Verbose " [keep] $($_.InputObject) $Required$DefaultTag" + $ScopesToRequest += $_.InputObject + $ScopesToKeep += $_.InputObject + } + } + } else { + Write-Verbose " [keep] $($_.InputObject) $Required$DefaultTag" + $ScopesToKeep += $_.InputObject + } + + } + + + # If scopes aren't changed and there is an existing credential, use the existing UserCredential. + # This will allow all cached service objects to use the same UserCredential object and receive updated tokens when it automatically refreshes. + If ($ExistingCredential -and ($ScopesToGrant.count -eq 0) -and ($ScopesToRevoke.count -eq 0)){ + Write-Verbose "No OAuth scope changes are required. Returning the existing UserCredential for user '$user'." + Return $ExistingCredential + } + Write-Verbose "Requesting a new UserCredential for OAuth scopes: $($ScopesToGrant.count) new, $($ScopesToKeep.count) unchanged, $($ScopesToRevoke.count) revoked" + } + + + + # Online Flows - Get user confirmation + # + # We will only get confirmation for explicit revocation of scopes + # If a new credential with additional scopes is being created, we will silently revoke the existing credential once it has become superseded. + If ($PSCmdlet.ParameterSetName -ne "Offline"){ + If (-not (($ScopesToRevoke.count -eq 0) -or $PSCmdlet.ShouldProcess($User, "Authorise $($ScopesToGrant.Count) new OAuth scopes and revoke $($ScopesToRevoke.count) existing OAuth scopes"))){ + Write-Verbose "Confirmation to revoke OAuth scopes was not provided. The existing UserCredential will be returned without modification." + Return $ExistingCredential + } + } + + + + # Online flows - Revoke existing OAuth Token + # + # If the token scopes are changing a new token will be generated. + # When creating a UserCredential the GoogleWebAuthorizationBroker loads the existing credential from disk (based on $tokenKey) without verifying if all scopes exist. + # To force fetching a new UserCredential from Google we need to make sure no entry in the diskDatastore exists for $tokenKey. + if ($ExistingCredential){ + Write-Verbose "Revoking the existing UserCredential." + Revoke-GSToken -UserCredential $ExistingCredential -confirm:$False + } + + + + # Online flows - Request the new OAuth Token + # Offline flow - Load the existing OAuth token from disk + Try { + Write-Verbose "Building UserCredential '$TokenKey' from ClientSecrets and prompting for authorization if necessary." + $Initializer = [Google.Apis.Auth.OAuth2.Flows.GoogleAuthorizationCodeFlow+Initializer]::new() + $Initializer.ClientSecrets = $script:_PSGSuiteClientSecrets + $Initializer.LoginHint = $User + # Incremental Authorization is not permitted for installed apps. https://developers.google.com/identity/protocols/oauth2/native-app + $Initializer.IncludeGrantedScopes = $False + + $CodeReceiver = [Google.Apis.Auth.OAuth2.LocalServerCodeReceiver]::new() + + $credential = [Google.Apis.Auth.OAuth2.GoogleWebAuthorizationBroker]::AuthorizeAsync( + $Initializer, + [string[]]@($ScopesToRequest), + $TokenKey, + [System.Threading.CancellationToken]::None, + $Datastore, + $CodeReceiver + ).GetAwaiter().GetResult() + + } Catch { + $PSCmdlet.ThrowTerminatingError($_) + } + + If ($null -eq $Credential){ + $PSCmdlet.ThrowTerminatingError((ThrowTerm "Failed to create UserCredential. The authorisation prompt was likely cancelled, please try the request again.")) + } + Write-Verbose "UserCredential '$TokenKey' has been created" + + + + # + # Validation - The emphasis is on security, if any validation steps fail the new token will be revoked and the user will need to complete the authorisation workflow again. + # + + + # Online flows - Validate the token user + # Offline flows - Validate the token user + # + # During interactive authorization the user account that authorizes the API access is controlled by the end user. This makes it possible that an unexpected user + # authorises the request. Proceeding with the unexpected user may cause incorrect resources to be modified, or other unintended consequences to arise. + # To prevent this we will check that the user attached to the authentication token (ID Token), is the user that was requested by the current command. + # + # In theory any tokens found on disk via the offline flow should be valid but we will re-validate the token again anyway to ensure it's validity. + If (-not $SkipUserValidation){ + + # If the token is stale we should refresh it first. + If ($credential.Token.IsStale){ + Write-Verbose "The UserCredential is stale and will be refreshed" + Try { + $Credential.RefreshTokenAsync([System.Threading.CancellationToken]::None).GetAwaiter().GetResult() | Out-Null + } Catch { + Write-Verbose "An error occured while refreshing the UserCredential's token. The token will be revoked!" + Revoke-GSToken -UserCredential $credential -confirm:$False + $PSCmdlet.ThrowTerminatingError($_) + } + } + + # Confirm that an IdToken was produced + If ($null -eq $credential.Token.IdToken){ + Write-Verbose "The UserCredential does not contain an ID token!" + Revoke-GSToken -UserCredential $credential -confirm:$False + $PSCmdlet.ThrowTerminatingError((ThrowTerm "Unable to validate the UserCredential's owner, an ID token was not found. It is mandatory that the OpenID scope be authorised during the interactive authorisation.")) + } + + # Parse and validate the ID token + Try { + $IDToken = [Google.Apis.Auth.GoogleJsonWebSignature]::ValidateAsync($credential.Token.IdToken, [Google.Apis.Auth.GoogleJsonWebSignature+ValidationSettings]::new()).GetAwaiter().GetResult() + } Catch { + Write-Verbose "The UserCredential's ID token failed validation. The token will be revoked!" + Revoke-GSToken -UserCredential $credential -confirm:$False + $PSCmdlet.ThrowTerminatingError($_) + } + + # Check the user who authorised the credential + If ($IDToken.Email -ne $User){ + Write-Verbose "A UserCredential was requested for '$User' but a UserCredential for '$($IDToken.Email)' was created." + Revoke-GSToken -UserCredential $Credential -Confirm:$False + $PSCmdlet.ThrowTerminatingError((ThrowTerm "A UserCredential was requested for '$User' but a UserCredential for '$($IDToken.Email)' was created. The token has been revoked.")) + } else { + Write-Verbose "The UserCredential was successfully validated for user '$User'" + } + + } else { + Write-Verbose "Skipping user validation. The -SkipUserValidation switch is present." + } + + + + + + # Online flows - Validate the token scopes + # + # During interactive authorization the user may deny one or more of the requested scopes while approving all others. + # We will check that the scopes included in the authorization token contain the minimum scopes that are needed to execute the current command. + # If any other scopes are missing, they will be re-requested the next time they are required for a command. + $TokenScopes = $Credential.Token.Scope.Split(' ') + If ($PSCmdlet.ParameterSetName -ne 'Offline'){ + If ($ScopesRequired.count){ + $MissingScopes = @() + ForEach ($RequiredScope in $ScopesRequired){ + If (-not $TokenScopes.Contains($RequiredScope)){ + Write-Verbose "The required OAuth scope '$RequiredScope' is not authorised." + $MissingScopes += $RequiredScope + } + } + If ($MissingScopes.count){ + $PSCmdlet.ThrowTerminatingError((ThrowTerm "The following scopes are required but they have not been authorised for use:`n$($MissingScopes -join "`n")")) + } + } + } + + # Save the newly created credential in memory for later use + $script:_PSGSuiteUserCredentials[$tokenKey] = $credential + + Write-Verbose "UserCredential for user '$user' was successfully created and includes $($TokenScopes.count) authorised OAuth scopes." + Return $Credential + + } + +} \ No newline at end of file diff --git a/PSGSuite/Private/EncryptionHelpers.ps1 b/PSGSuite/Private/EncryptionHelpers.ps1 index f12efe08..e985f665 100644 --- a/PSGSuite/Private/EncryptionHelpers.ps1 +++ b/PSGSuite/Private/EncryptionHelpers.ps1 @@ -22,10 +22,10 @@ function Get-GSDecryptedConfig { @{l = 'JSONServiceAccountKey'; e = {Invoke-GSDecrypt $_.JSONServiceAccountKey}} @{l = 'ClientSecretsPath'; e = { Invoke-GSDecrypt $_.ClientSecretsPath } } @{l = 'ClientSecrets'; e = { Invoke-GSDecrypt $_.ClientSecrets } } - @{l = 'ClientSecretOAuthScopes'; e = { + @{l = 'ClientSecretScopes'; e = { $array = @() - If ($_.ClientSecretOAuthScopes){ - foreach ($entry in $_.ClientSecretOAuthScopes){ + If ($_.ClientSecretScopes){ + foreach ($entry in $_.ClientSecretScopes){ $array += Invoke-GSDecrypt $Entry } } diff --git a/PSGSuite/Public/Authentication/Get-GSScope.ps1 b/PSGSuite/Public/Authentication/Get-GSScope.ps1 new file mode 100644 index 00000000..542cc532 --- /dev/null +++ b/PSGSuite/Public/Authentication/Get-GSScope.ps1 @@ -0,0 +1,92 @@ +Function Get-GSScope { + <# + .SYNOPSIS + Returns the OAuth scopes that PSGSuite has been authorized to access for the specified user. + + .DESCRIPTION + Returns the OAuth scopes that PSGSuite has been authorized to access for the specified user. + + This is only supported when the PSGSuite authentication method is set to Client-Secrets-OAuth. + + .OUTPUTS + The list of authorised OAuth scopes for the specified user. + + .PARAMETER User + The primary email of the user whose authorized OAuth scopes are to be returned. 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 + + .EXAMPLE + Returns the list of authorized OAuth scopes for the specified user. + + PS > Get-GSScope -User $User + + .NOTES + It is only possible to get the OAuth scopes that have been authorized when using the Client-Secrets-OAuth authentication method. All other authentication methods will return no results. + + .LINK + https://psgsuite.io/Function%20Help/Authentication/Get-GSScope/ + #> + [OutputType([String[]])] + [cmdletbinding()] + Param( + [parameter(Mandatory = $false,Position = 0,ValueFromPipeline = $true,ValueFromPipelineByPropertyName = $true)] + [ValidateNotNullOrEmpty()] + [String] + $User = $Script:PSGSuite.AdminEmail + ) + + Process { + + $AuthMethod = Get-PSGSuiteAuthenticationMethod + If ($AuthMethod -ne 'Client-Secrets-OAuth'){ + Write-Warning "It is only possible to get the authorized OAuth scopes when the PSGSuite authentication method is Client-Secrets-OAuth. The currrent PSGSuite authentication method is '$AuthMethod'." + Return + } + + Resolve-Email ([ref]$User) + + # The $TokenKey is used as the key when retrieving tokens from disk or the in-memory user credential cache. + # We will create the key such that all tokens will be unique per PSGSuite config and user combination. Allowing the same user to have different + # tokens per PSGSuite configuration. + # https://github.com/googleapis/google-api-dotnet-client/issues/2709 + $TokenKey = @($script:PSGSuite.ConfigName, $User) -Join '-' + + Write-Verbose "Getting the authorized OAuth scopes for user '$user'" + + # Try the memory cache first before fetching from disk + $AuthorizedToken = If ($script:_PSGSuiteUserCredentials){ + If ($script:_PSGSuiteUserCredentials.ContainsKey($TokenKey)){ + Write-Verbose "UserCredential '$TokenKey' was found in memory" + $script:_PSGSuiteUserCredentials[$TokenKey].Token + } + } + + # Fallback to the disk cache + If (-not $AuthorizedToken){ + # Initialize the FileDataStore used for storing and retrieving cached OAuth tokens. + $Datastore = New-Object 'Google.Apis.Util.Store.FileDataStore' -ArgumentList $Script:_PSGSuiteCredPath,$true + # Search the datastore + $AuthorizedToken = $Datastore.getAsync[Google.Apis.Auth.OAuth2.Responses.TokenResponse]($TokenKey).GetAwaiter().GetResult() + If ($AuthorizedToken){ + Write-Verbose "UserCredential '$TokenKey' was found on disk" + } + } + + # Get the scopes from the token + If ($AuthorizedToken){ + + [string[]]$AuthorizedScopes = $AuthorizedToken.Scope -Split " " | Sort-Object + Write-Verbose "$($AuthorizedScopes.count) authorized OAuth scopes were found for user '$User'" + + If ($AuthorizedScopes){ + Return $AuthorizedScopes + } + + } else { + Write-Verbose "UserCredential '$TokenKey' does not exist" + } + + } + +} \ No newline at end of file diff --git a/PSGSuite/Public/Authentication/Get-PSGSuiteAuthenticationMethod.ps1 b/PSGSuite/Public/Authentication/Get-PSGSuiteAuthenticationMethod.ps1 new file mode 100644 index 00000000..e2a78951 --- /dev/null +++ b/PSGSuite/Public/Authentication/Get-PSGSuiteAuthenticationMethod.ps1 @@ -0,0 +1,46 @@ +Function Get-PSGSuiteAuthenticationMethod { + <# + .SYNOPSIS + Returns the authentication method that will be used based on the values that have been provided in the PSGSuite configuration. + + .DESCRIPTION + Returns the authentication method that will be used based on the values that have been provided in the PSGSuite configuration. + + If multiple authentication methods are present in the PSGSuite configuration. The authentication method will be selected based on the following priority order: + 1. Service-Account-JSON-Key + 2. Service-Account-P12-Key + 3. Client-Secrets-OAuth + + The following values can be returned: + - Service-Account-JSON-Key + - Service-Account-P12-Key + - Client-Secrets-OAuth + - Unknown + + .EXAMPLE + PS > Get-PSGSuiteAuthenticationMethod + + Service-Account-P12-Key + + .LINK + https://psgsuite.io/Function%20Help/Authentication/Get-PSGSuiteAuthenticationMethod/ + #> + [OutputType([String])] + [cmdletbinding()] + Param() + + Process { + if ($script:PSGSuite.JSONServiceAccountKey -or $script:PSGSuite.JSONServiceAccountKeyPath){ + $Method = 'Service-Account-JSON-Key' + } elseif ($script:PSGSuite.P12KeyPath -or $script:PSGSuite.P12Key -or $script:PSGSuite.P12KeyObject) { + $Method = 'Service-Account-P12-Key' + } elseif ($script:PSGSuite.ClientSecretsPath -or $script:PSGSuite.ClientSecrets) { + $Method = 'Client-Secrets-OAuth' + } else { + $Method = 'Unknown' + } + Write-Verbose "The current PSGSuite authentication method is '$Method'" + Return $Method + } + +} \ No newline at end of file diff --git a/PSGSuite/Public/Authentication/Get-PSGSuiteOAuthScope.ps1 b/PSGSuite/Public/Authentication/Get-PSGSuiteScope.ps1 similarity index 68% rename from PSGSuite/Public/Authentication/Get-PSGSuiteOAuthScope.ps1 rename to PSGSuite/Public/Authentication/Get-PSGSuiteScope.ps1 index ee50656e..e50bf5fc 100644 --- a/PSGSuite/Public/Authentication/Get-PSGSuiteOAuthScope.ps1 +++ b/PSGSuite/Public/Authentication/Get-PSGSuiteScope.ps1 @@ -1,4 +1,4 @@ -Function Get-PSGSuiteOAuthScope { +Function Get-PSGSuiteScope { <# .SYNOPSIS Returns the OAuth scopes used by PSGSuite @@ -32,7 +32,7 @@ Function Get-PSGSuiteOAuthScope { Returns the unique scope values only .EXAMPLE - PS > Get-PSGSuiteOAuthScope -Service Google.Apis.Slides.v1.SlidesService + PS > Get-PSGSuiteScope -Service Google.Apis.Slides.v1.SlidesService Function Service Scope -------- ------- ----- @@ -40,13 +40,13 @@ Function Get-PSGSuiteOAuthScope { Edit-GSPresentation Google.Apis.Slides.v1.SlidesService https://www.googleapis.com/auth/drive .EXAMPLE - PS > Get-PSGSuiteOAuthScope -Function 'Get-GSUser' -ValueOnly + PS > Get-PSGSuiteScope -Function 'Get-GSUser' -ValueOnly https://www.googleapis.com/auth/admin.directory.user https://www.googleapis.com/auth/admin.directory.user.readonly .EXAMPLE - PS > Get-PSGSuiteOAuthScope -Scope https://www.googleapis.com/auth/chat.bot + PS > Get-PSGSuiteScope -Scope https://www.googleapis.com/auth/chat.bot Function Service Scope -------- ------- ----- @@ -58,7 +58,7 @@ Function Get-PSGSuiteOAuthScope { Get-GSChatMember Google.Apis.HangoutsChat.v1.HangoutsChatService https://www.googleapis.com/auth/chat.bot .LINK - https://psgsuite.io/Function%20Help/Authentication/Get-PSGSuiteOAuthScope/ + https://psgsuite.io/Function%20Help/Authentication/Get-PSGSuiteScope/ .LINK https://developers.google.com/identity/protocols/oauth2/scopes @@ -88,7 +88,7 @@ Function Get-PSGSuiteOAuthScope { ) begin { - If (-not $Script:_PSGSuiteOAuthScopes){ + If (-not $script:_PSGSuiteScopes){ $PSCmdlet.ThrowTerminatingError((ThrowTerm "The PSGSuite scopes were not found")) } } @@ -99,11 +99,11 @@ Function Get-PSGSuiteOAuthScope { 'GetService' { If ($ValueOnly){ - Write-Verbose "Getting all unique scope values objects for service '$Service'" - $script:_PSGSuiteOAuthScopes | Where-Object {$_.Service -eq $Service} | Select-Object -ExpandProperty 'Scope' -Unique + Write-Verbose "Getting all unique OAuth scope values for service '$Service'" + $script:_PSGSuiteScopes | Where-Object {$_.Service -eq $Service} | Select-Object -ExpandProperty 'Scope' -Unique } else { - Write-Verbose "Getting all scope objects for service '$Service'" - $script:_PSGSuiteOAuthScopes | Where-Object {$_.Service -eq $Service} | ForEach-Object { + Write-Verbose "Getting all OAuth scope objects for service '$Service'" + $script:_PSGSuiteScopes | Where-Object {$_.Service -eq $Service} | ForEach-Object { $_.psobject.Copy() } } @@ -111,11 +111,11 @@ Function Get-PSGSuiteOAuthScope { 'GetFunction' { If ($ValueOnly){ - Write-Verbose "Getting all unique scope values for function '$Function'" - $script:_PSGSuiteOAuthScopes | Where-Object {$_.Function -eq $Function} | Select-Object -ExpandProperty 'Scope' -Unique + Write-Verbose "Getting all unique OAuth scope values for function '$Function'" + $script:_PSGSuiteScopes | Where-Object {$_.Function -eq $Function} | Select-Object -ExpandProperty 'Scope' -Unique } else { - Write-Verbose "Getting all scope objects for function '$Function'" - $script:_PSGSuiteOAuthScopes | Where-Object {$_.Function -eq $Function} | ForEach-Object { + Write-Verbose "Getting all OAuth scope objects for function '$Function'" + $script:_PSGSuiteScopes | Where-Object {$_.Function -eq $Function} | ForEach-Object { $_.psobject.Copy() } } @@ -123,11 +123,11 @@ Function Get-PSGSuiteOAuthScope { 'GetScope' { If ($ValueOnly){ - Write-Verbose "Getting all unique scope values for scope '$Scope'" - $script:_PSGSuiteOAuthScopes | Where-Object {$_.Scope -eq $Scope} | Select-Object -ExpandProperty 'Scope' -Unique + Write-Verbose "Getting all unique OAuth scope values for scope '$Scope'" + $script:_PSGSuiteScopes | Where-Object {$_.Scope -eq $Scope} | Select-Object -ExpandProperty 'Scope' -Unique } else { - Write-Verbose "Getting all scope objects for scope '$Scope'" - $script:_PSGSuiteOAuthScopes | Where-Object {$_.Scope -eq $Scope} | ForEach-Object { + Write-Verbose "Getting all OAuth scope objects for scope '$Scope'" + $script:_PSGSuiteScopes | Where-Object {$_.Scope -eq $Scope} | ForEach-Object { $_.psobject.Copy() } } @@ -135,11 +135,11 @@ Function Get-PSGSuiteOAuthScope { 'GetAll' { If ($ValueOnly){ - Write-Verbose "Getting all unique scope values" - $script:_PSGSuiteOAuthScopes | Select-Object -ExpandProperty 'Scope' -Unique + Write-Verbose "Getting all unique OAuth scope values" + $script:_PSGSuiteScopes | Select-Object -ExpandProperty 'Scope' -Unique } else { - Write-Verbose "Getting all scope objects" - $script:_PSGSuiteOAuthScopes | ForEach-Object { + Write-Verbose "Getting all OAuth scope objects" + $script:_PSGSuiteScopes | ForEach-Object { $_.psobject.Copy() } } diff --git a/PSGSuite/Public/Authentication/Grant-GSScope.ps1 b/PSGSuite/Public/Authentication/Grant-GSScope.ps1 new file mode 100644 index 00000000..a9d66dee --- /dev/null +++ b/PSGSuite/Public/Authentication/Grant-GSScope.ps1 @@ -0,0 +1,109 @@ +Function Grant-GSScope { + <# + .SYNOPSIS + Invokes interactive authorisation for the given user and requests authorisation for the specified OAuth scopes. + + .DESCRIPTION + Invokes interactive authorisation for the given user and requests authorisation for the specified OAuth scopes. + + This is only supported when the PSGSuite authentication method is set to Client-Secrets-OAuth. + + .OUTPUTS + The list of authorised OAuth scopes for the specified user. + + .PARAMETER User + The primary email of the user whose authorized OAuth scopes are to be updated. 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 Scope + The OAuth scopes to authorise. + + Accepted values are: + * OAuth scope - The value of a specific OAuth scope. eg 'https://www.googleapis.com/auth/admin.directory.user' + * PSGSuite function - The name of a PSGSuite function. eg 'Get-GSUser' + * API service - The service string for a specific Google API. eg 'Google.Apis.Admin.Directory.directory_v1.DirectoryService' + + When an API service or PSGSuite function is specified the values will be resolved to their respective OAuth scopes. + + See PSGSuite help or use `Get-PSGSuiteScope` to see the list of OAuth scopes, functions and API services. + + All default OAuth scopes will always be authorised regardless if they are included with the `-scope` parameter or not. + + .PARAMETER ExcludeSupplementalScopes + Excludes and revokes authorisation for all supplemental OAuth scopes that were not included with the `-scope` parameter. + + .EXAMPLE + Invokes the interactive authorisation workflow prompting the user to authorise the OAuth scopes: + - All supplemental OAuth scopes with an existing authorisation + - All default OAuth scopes + - OAuth scope 'https://www.googleapis.com/auth/admin.directory.user' + + PS > Get-GSScope -User 'user@email.com' -Scope 'https://www.googleapis.com/auth/admin.directory.user' + + .EXAMPLE + Invokes the interactive authorisation workflow prompting the user to authorise the OAuth scopes: + - Default OAuth scopes found in the `ClientSecretsScopes` configuration parameter + - OAuth scope 'https://www.googleapis.com/auth/admin.directory.user' + + Any other supplemental OAuth scopes will have their authorisation revoked. + + PS > Get-GSScope -User 'user@email.com' -ExcludeSupplementalScopes -Scope 'https://www.googleapis.com/auth/admin.directory.user' + + .NOTES + It is only possible to change the authorized OAuth scopes when using the Client-Secrets-OAuth authentication method. All other authentication methods will return an error. + + The default OAuth scopes are configured via the PSGSuite `ClientSecretsScopes` configuration parameter. See `Set-PSGSuiteConfig` for further details. + + A supplemental OAuth scope is any OAuth scope that is not configured as a default OAuth scope. + + .LINK + https://psgsuite.io/Function%20Help/Authentication/Grant-GSScope/ + #> + [OutputType([String[]])] + [cmdletbinding(SupportsShouldProcess, ConfirmImpact='High')] + Param( + [parameter(Mandatory = $false)] + [ValidateNotNullOrEmpty()] + [String] + $User = $Script:PSGSuite.AdminEmail, + [parameter(Mandatory = $false)] + [ValidateSet([PSGSuiteValidScopeIdentifierValues])] + [String[]]$Scope = @(), + [parameter(Mandatory = $false)] + [Switch]$ExcludeSupplementalScopes + ) + + Process { + + $AuthMethod = Get-PSGSuiteAuthenticationMethod + If ($AuthMethod -ne 'Client-Secrets-OAuth'){ + $PSCmdlet.ThrowTerminatingError((ThrowTerm "It is only possible to change the authorized OAuth scopes when the PSGSuite authentication method is Client-Secrets-OAuth. The currrent PSGSuite authentication method is '$AuthMethod'.")) + } + + Resolve-Email ([ref]$User) + + If ($Scope.Count){ + $ResolvedScopes = Resolve-PSGSuiteScope -scope $Scope + } else { + Write-Verbose "No Oauth scopes were specified. The default OAuth scopes will be granted instead." + $ResolvedScopes = Resolve-PSGSuiteScope -scope $Script:PSGSuite.ClientSecretScopes + } + + If ((-not $ExcludeSupplementalScopes) -or $PSCmdlet.ShouldProcess($User, "Grant $($ResolvedScopes.count) OAuth scopes and revoke all supplemental OAuth scopes")){ + Write-Verbose "Requesting grant of $($ResolvedScopes.count) OAuth scopes for user '$User'" + Try { + $Credential = New-GoogleUserCredential -Scope $ResolvedScopes -User $User -ExcludeSupplementalScopes:$ExcludeSupplementalScopes -confirm:$false + } Catch { + $PSCmdlet.ThrowTerminatingError($_) + } + $Credential.Token.Scope -split ' ' | Sort-Object + } else { + # Return the currently authorised scopes without making any changes + Write-Verbose "User confirmation was not provided. No OAuth scopes were granted." + Get-GSScope -User $User + } + + } + +} \ No newline at end of file diff --git a/PSGSuite/Public/Authentication/New-GoogleService.ps1 b/PSGSuite/Public/Authentication/New-GoogleService.ps1 index a7d19f7e..723c3acd 100644 --- a/PSGSuite/Public/Authentication/New-GoogleService.ps1 +++ b/PSGSuite/Public/Authentication/New-GoogleService.ps1 @@ -55,100 +55,27 @@ function New-GoogleService { $script:_PSGSuiteSessions[$sessionKey] | Select-Object -ExpandProperty Service } else { - if ($script:PSGSuite.JSONServiceAccountKey -or $script:PSGSuite.JSONServiceAccountKeyPath) { - Write-Verbose "Building ServiceAccountCredential from JSONServiceAccountKey as user '$User'" - try { - if (-not $script:PSGSuite.JSONServiceAccountKey) { - $script:PSGSuite.JSONServiceAccountKey = ([System.IO.File]::ReadAllBytes($script:PSGSuite.JSONServiceAccountKeyPath)) - Set-PSGSuiteConfig -ConfigName $script:PSGSuite.ConfigName -JSONServiceAccountKey $script:PSGSuite.JSONServiceAccountKey -Verbose:$false + + Try { + $AuthMethod = Get-PSGSuiteAuthenticationMethod + Switch ($AuthMethod){ + 'Service-Account-JSON-Key' { + $Credential = New-GoogleServiceAccountCredential -Scope $Scope -User $User } - $stream = New-Object System.IO.MemoryStream $([System.Text.Encoding]::ASCII.GetBytes($script:PSGSuite.JSONServiceAccountKey)), $null - $credential = ([Google.Apis.Auth.OAuth2.GoogleCredential]::FromStream($stream)).CreateWithUser($User).CreateScoped($Scope).UnderlyingCredential - } - catch { - $PSCmdlet.ThrowTerminatingError($_) - } - finally { - if ($stream) { - $stream.Close() + 'Service-Account-P12-Key' { + $Credential = New-GoogleServiceAccountCredential -Scope $Scope -User $User } - } - } - elseif ($script:PSGSuite.P12KeyPath -or $script:PSGSuite.P12Key -or $script:PSGSuite.P12KeyObject) { - try { - Write-Verbose "Building ServiceAccountCredential from P12Key as user '$User'" - if ($script:PSGSuite.P12KeyPath -or $script:PSGSuite.P12Key) { - 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:PSGSuite.P12KeyPassword) { - $P12KeyPassword = $script:PSGSuite.P12KeyPassword - } - else { - $P12KeyPassword = "notasecret" - } - $certificate = New-Object 'System.Security.Cryptography.X509Certificates.X509Certificate2' -ArgumentList ([System.Byte[]]$script:PSGSuite.P12Key),$P12KeyPassword,([System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable) + 'Client-Secrets-OAuth' { + $Credential = New-GoogleUserCredential -Scope $Scope -User $User } - else { - $certificate = $script:PSGSuite.P12KeyObject + Default { + $PSCmdlet.ThrowTerminatingError((ThrowTerm "The current config '$($script:PSGSuite.ConfigName)' does not contain a JSONServiceAccountKeyPath, P12KeyPath, or 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 three credential types.")) } - $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) - - } - catch { - $PSCmdlet.ThrowTerminatingError($_) } + } 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 - } - $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, - $(New-Object 'Google.Apis.Util.Store.FileDataStore' -ArgumentList $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() - } - } - } - else { - $PSCmdlet.ThrowTerminatingError((ThrowTerm "The current config '$($script:PSGSuite.ConfigName)' does not contain a JSONServiceAccountKeyPath, P12KeyPath, or 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 three credential types.")) - } + $svc = New-Object "$ServiceType" (New-Object 'Google.Apis.Services.BaseClientService+Initializer' -Property @{ HttpClientInitializer = $credential ApplicationName = "PSGSuite" diff --git a/PSGSuite/Public/Authentication/Resolve-PSGSuiteScope.ps1 b/PSGSuite/Public/Authentication/Resolve-PSGSuiteScope.ps1 new file mode 100644 index 00000000..feb3984a --- /dev/null +++ b/PSGSuite/Public/Authentication/Resolve-PSGSuiteScope.ps1 @@ -0,0 +1,79 @@ +Function Resolve-PSGSuiteScope { + <# + .SYNOPSIS + Resolves the provided OAuth scope identifiers to their OAuth scope values. + + .DESCRIPTION + Resolves the provided OAuth scope identifiers to their OAuth scope values. + + .PARAMETER Scope + The OAuth scope identifiers to resolve. + + Accepted values are: + - OAuth scope - The value of a specific OAuth scope. eg 'https://www.googleapis.com/auth/admin.directory.user' + - PSGSuite function - The name of a PSGSuite function. eg 'Get-GSUser' + - API service - The service string for a specific Google API. eg 'Google.Apis.Admin.Directory.directory_v1.DirectoryService' + + .PARAMETER DefaultScopes + Resolves the default OAuth scopes from the `ClientSecretScopes` configuration parameter. + + .EXAMPLE + PS> Resolve-PSGSuiteScope -Scope 'Get-GSUser', 'Google.Apis.Calendar.v3.CalendarService' + + https://www.googleapis.com/auth/admin.directory.user + https://www.googleapis.com/auth/admin.directory.user.readonly + https://www.googleapis.com/auth/calendar + + .LINK + https://psgsuite.io/Function%20Help/Authentication/Resolve-PSGSuiteScope/ + + #> + + [CmdletBinding()] + Param ( + [parameter(Mandatory=$true, ParameterSetName='Scope')] + [ValidateSet([PSGSuiteValidScopeIdentifierValues])] + [String[]] + $Scope, + [parameter(Mandatory = $true, ParameterSetName = 'DefaultScopes')] + [Switch]$DefaultScopes + ) + Process { + + If ($PSCmdlet.ParameterSetName -eq "DefaultScopes"){ + Write-Verbose "Resolving the default OAuth scopes from the current configuration" + $Scope = @() + ForEach ($Value in $Script:PSGSuite.ClientSecretScopes){ + $Scope += $Value + } + #Add the 'unserinfo.email' and 'openid' OAuth scope so that the authenticated user's email address is included in the authentication response. + # These scopes are mandatory and always required so that the token user can be validated. + $Scope += "https://www.googleapis.com/auth/userinfo.email" + $Scope += "openid" + $Scope = $Scope | Select-Object -Unique + } + + Write-Verbose "Resolving OAuth scopes from $($Scope.count) input values" + $Output = ForEach ($Value in $Scope){ + Switch -Regex ($Value){ + "^Google\.Apis" { + Get-PSGSuiteScope -Service $Value -ValueOnly + } + "^https://" { + $Value + } + "^openid" { + "openid" + } + Default { + Get-PSGSuiteScope -Function $Value -ValueOnly + } + } + } + $Output = $Output | Select-Object -Unique | Sort-Object + Write-Verbose "Resolved $($Output.count) unique OAuth scopes" + $Output + + } + +} \ No newline at end of file diff --git a/PSGSuite/Public/Authentication/Revoke-GSScope.ps1 b/PSGSuite/Public/Authentication/Revoke-GSScope.ps1 new file mode 100644 index 00000000..ae814263 --- /dev/null +++ b/PSGSuite/Public/Authentication/Revoke-GSScope.ps1 @@ -0,0 +1,131 @@ +Function Revoke-GSScope { + <# + .SYNOPSIS + Invokes interactive authorisation for the given user and revokes authorisation for the specified OAuth scopes. + + .DESCRIPTION + Invokes interactive authorisation for the given user and revokes authorisation for the specified OAuth scopes. + + This is only supported when the PSGSuite authentication method is set to Client-Secrets-OAuth. + + .OUTPUTS + The list of authorised OAuth scopes for the specified user. + + .PARAMETER User + The primary email of the user whose authorized OAuth scopes are to be updated. 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 Scope + The OAuth scopes to revoke. + + Accepted values are: + * OAuth scope - The value of a specific OAuth scope. eg 'https://www.googleapis.com/auth/admin.directory.user' + * PSGSuite function - The name of a PSGSuite function. eg 'Get-GSUser' + * API service - The service string for a specific Google API. eg 'Google.Apis.Admin.Directory.directory_v1.DirectoryService' + + When an API service or PSGSuite function is specified the values will be resolved to their respective OAuth scopes. + + See PSGSuite help or use `Get-PSGSuiteScope` to see the list of OAuth scopes, functions and API services. + + The default OAuth scopes will always be authorised regardless if they are included with the `-scope` parameter or not. + + .PARAMETER SupplementalScopes + Revokes authorisation for all supplemental OAuth scopes and requests authorisation for all default OAuth scopes. + + .PARAMETER AllScopes + Revokes authorisation for all supplemental and default OAuth scopes. + + .EXAMPLE + Invokes the interactive authorisation workflow and revokes authorisation for the 'https://www.googleapis.com/auth/admin.directory.user' OAuth scope. + + PS > Revoke-GSScope -User 'user@email.com' -Scope 'https://www.googleapis.com/auth/admin.directory.user' + + .EXAMPLE + Invokes the interactive authorisation workflow revokes authorisation for all supplemental OAuth scopes. + + PS > Get-GSScope -User 'user@email.com' -AllSupplementalScopes + + .EXAMPLE + Revokes authorisation for all supplemental and default OAuth scopes. + + PS > Get-GSScope -User 'user@email.com' -AllScopes + + .NOTES + It is only possible to change the authorized OAuth scopes when using the Client-Secrets-OAuth authentication method. All other authentication methods will return an error. + + The default OAuth scopes are configured via the PSGSuite `ClientSecretsScopes` configuration parameter. See `Set-PSGSuiteConfig` for further details. + + A supplemental OAuth scope is any OAuth scope that is not configured as a default OAuth scope. + + .LINK + https://psgsuite.io/Function%20Help/Authentication/Revoke-GSScope/ + #> + [OutputType([String[]])] + [cmdletbinding(SupportsShouldProcess, ConfirmImpact='High')] + Param( + [parameter(Mandatory = $false, ParameterSetName = 'Scope')] + [parameter(Mandatory = $false, ParameterSetName = 'Reset')] + [parameter(Mandatory = $false, ParameterSetName = 'Revoke')] + [ValidateNotNullOrEmpty()] + [String] + $User = $Script:PSGSuite.AdminEmail, + [parameter(Mandatory = $true, ParameterSetName = 'Scope')] + [ValidateSet([PSGSuiteValidScopeIdentifierValues])] + [String[]]$Scope, + [parameter(Mandatory = $true, ParameterSetName = 'Reset')] + [Switch]$SupplementalScopes, + [parameter(Mandatory = $true, ParameterSetName = 'Revoke')] + [Switch]$AllScopes + + ) + + Process { + + $AuthMethod = Get-PSGSuiteAuthenticationMethod + If ($AuthMethod -ne 'Client-Secrets-OAuth'){ + $PSCmdlet.ThrowTerminatingError((ThrowTerm "It is only possible to change the authorized OAuth scopes when the PSGSuite authentication method is Client-Secrets-OAuth. The currrent PSGSuite authentication method is '$AuthMethod'.")) + } + + Resolve-Email ([ref]$User) + + If ($PSCmdlet.ParameterSetName -eq 'Revoke'){ + + write-verbose "Attempting to revoke all authorization for user '$User'" + If ($PSCmdlet.ShouldProcess($User, "Revoke all OAuth scopes")){ + Revoke-GSToken -User $User -confirm:$false + } else { + Write-Warninge "Confirmation was not provided. No OAuth scopes were revoked." + } + + } else { + + $Params = @{} + If ($PSCmdlet.ParameterSetName -eq 'Scope'){ + $ResolvedScopes = Resolve-PSGSuiteScope -scope $Scope + $Message = "Revoke $($ResolvedScopes.count) OAuth Scopes" + $Params['ExcludeScope'] = $ResolvedScopes + } else { + $Message = "Revoke all supplemental OAuth scopes" + $Params['ExcludeSupplementalScopes'] = $True + } + + write-verbose "Attempting to $Message for user '$User'" + If ($PSCmdlet.ShouldProcess($User, $Message)){ + Try { + $Credential = New-GoogleUserCredential -User $User @Params -confirm:$false + } Catch { + $PSCmdlet.ThrowTerminatingError($_) + } + $Credential.Token.Scope -split ' ' | Sort-Object + } else { + # Return the currently authorised scopes without making any changes + Write-Warning "User confirmation was not provided. No OAuth scopes were revoked." + Get-GSScope -User $User + } + + } + + } + +} \ No newline at end of file diff --git a/PSGSuite/Public/Authentication/Revoke-GSToken.ps1 b/PSGSuite/Public/Authentication/Revoke-GSToken.ps1 new file mode 100644 index 00000000..549c7fb8 --- /dev/null +++ b/PSGSuite/Public/Authentication/Revoke-GSToken.ps1 @@ -0,0 +1,91 @@ +Function Revoke-GSToken { + <# + .SYNOPSIS + Revokes the OAuth authorization token that was granted to PSGSuite for the specified user. + + .DESCRIPTION + Revokes the OAuth authorization token that was granted to PSGSuite for the specified user. + + No output is produced by this function. If the operation fails an exception will be thrown. + + .PARAMETER User + The user who is to have their OAuth authorization revoked. + + .PARAMETER UserCredential + The userCredential containing the OAuth authorization token that is to be revoked. + + .PARAMETER NoDelete + Prevents the token being deleted from the disk cache after it has been revoked. + + .NOTES + It is only possible to revoke OAuth tokens that were generated using the Client-Secrets-OAuth authenication method. + + .EXAMPLE + PS > Revoke-GSToken -User 'user@email.com' + + .LINK + https://psgsuite.io/Function%20Help/Authentication/Revoke-GSToken/ + + #> + [cmdletbinding(SupportsShouldProcess, ConfirmImpact='High')] + Param( + [parameter(Mandatory = $false, ParameterSetName = "RevokeUser")] + [ValidateNotNullOrEmpty()] + [String] + $User = $Script:PSGSuite.AdminEmail, + [parameter(Mandatory = $true, ParameterSetName = "RevokeUserCredential")] + [Google.Apis.Auth.OAuth2.UserCredential] + $UserCredential, + [parameter(Mandatory = $false, ParameterSetName = "RevokeUser")] + [parameter(Mandatory = $false, ParameterSetName = "RevokeUserCredential")] + [Switch] + $NoDelete + + ) + + Process { + + # Get the user's UserCredential + If ($PSCmdlet.ParameterSetName -eq "RevokeUser"){ + $UserCredential = New-GoogleUserCredential -User $User -SkipUserValidation -Offline + If ($UserCredential){ + Write-Verbose "Revoking cached UserCredential for user '$User'." + } else { + Write-Warning "Nothing to revoke! No UserCredential was found for user '$User'." + Return + } + } + + If ($PSCmdlet.ShouldProcess($UserCredential.UserId)){ + + Try { + If ($NoDelete){ + # To prevent deleting the token from the disk cache we will call revoke with a non-existent TokenKey that will silently fail on deletion + # Ideally we would call $UserCredential.Flow.RevokeTokenAsync('NonexistentKey', $AccessToken, ...) but Flow is not Common Language Specification (CLS) compliant and errors out. + # So instead we will repackage the token into a new UserCredential referencing a non-existent key, and trigger revocation from there. + $repackagedCredential = [Google.Apis.Auth.OAuth2.UserCredential]::new($UserCredential.Flow, 'NonexistentKey', $UserCredential.Token) + $repackagedCredential.RevokeTokenAsync([System.Threading.CancellationToken]::None).GetAwaiter().GetResult() | Out-Null + Write-Verbose "Successfully revoked UserCredential '$($UserCredential.UserId)' and persisted on disk." + } else { + $UserCredential.RevokeTokenAsync([System.Threading.CancellationToken]::None).GetAwaiter().GetResult() | Out-Null + Write-Verbose "Successfully revoked UserCredential '$($UserCredential.UserId)' and deleted from disk." + } + } Catch { + $PSCmdlet.ThrowTerminatingError($_.Exception) + } + + # Clear the Service Cache so that all future commands will require a new token + Clear-PSGSuiteServiceCache + + # Clear the User Credential from memory + if ($script:_PSGSuiteUserCredentials -and $script:_PSGSuiteUserCredentials.ContainsKey($UserCredential.UserId)) { + Write-Verbose "Removing UserCredential '$($UserCredential.UserID)' from memory." + $script:_PSGSuiteUserCredentials.Remove($UserCredential.UserId) + } + + } else { + Write-Verbose "Confirmation was not provided. No attempt was made to revoke the UserCredential." + } + + } +} \ No newline at end of file diff --git a/PSGSuite/Public/Configuration/Export-PSGSuiteConfig.ps1 b/PSGSuite/Public/Configuration/Export-PSGSuiteConfig.ps1 index e1e9ec56..c2526d8f 100644 --- a/PSGSuite/Public/Configuration/Export-PSGSuiteConfig.ps1 +++ b/PSGSuite/Public/Configuration/Export-PSGSuiteConfig.ps1 @@ -41,7 +41,7 @@ function Export-PSGSuiteConfig { Process { try { Write-Verbose "Exporting config '$ConfigName' to path: $Path" - $baseConf | Select-Object ConfigName,P12Key,ClientSecrets,ClientSecretOAuthScopes,AppEmail,AdminEmail,CustomerId,Domain,Preference | ConvertTo-Json -Depth 5 -Compress -Verbose:$false | Set-Content -Path $Path -Verbose:$false + $baseConf | Select-Object ConfigName,P12Key,ClientSecrets,ClientSecretScopes,AppEmail,AdminEmail,CustomerId,Domain,Preference | ConvertTo-Json -Depth 5 -Compress -Verbose:$false | Set-Content -Path $Path -Verbose:$false } catch { $PSCmdlet.ThrowTerminatingError($_) diff --git a/PSGSuite/Public/Configuration/Set-PSGSuiteConfig.ps1 b/PSGSuite/Public/Configuration/Set-PSGSuiteConfig.ps1 index a53e620c..aa4754a7 100644 --- a/PSGSuite/Public/Configuration/Set-PSGSuiteConfig.ps1 +++ b/PSGSuite/Public/Configuration/Set-PSGSuiteConfig.ps1 @@ -30,7 +30,7 @@ function Set-PSGSuiteConfig { .PARAMETER ClientSecrets The string contents of the Client Secrets JSON file downloaded from the Google Developer's Console. Using the ClientSecrets JSON will prompt the user to complete OAuth2 authentication in their browser on the first run and store the retrieved Refresh and Access tokens in the user's home directory. If JSONServiceAccountKeyPath or P12KeyPath is also specified, ClientSecrets will be ignored. - .PARAMETER ClientSecretOAuthScopes + .PARAMETER ClientSecretScopes The list of OAuth scopes that are requested by default when Client Secrets authentication is used. If no OAuth scopes are specified or a command requires an OAuth scope that is not included in this list, PSGSuite will fallback to requesting the missing OAuth scopes when they are used. Accepted values are: @@ -40,7 +40,7 @@ function Set-PSGSuiteConfig { When an API service or PSGSuite function is specified the values will be resolved to their respective OAuth scopes. - See PSGSuite help or use `Get-PSGSuiteOAuthScope` to see the list of OAuth scopes, functions and API services. + See PSGSuite help or use `Get-PSGSuiteScope` to see the list of OAuth scopes, functions and API services. .PARAMETER AppEmail The application email from the Google Developer's Console. This typically looks like the following: @@ -92,7 +92,7 @@ function Set-PSGSuiteConfig { This builds a config names "personal" and sets it as the default config .EXAMPLE - Set-PSGSuiteConfig -ConfigName "personal" -ClientSecretsPath "C:\Keys\Client_Secret.json" -ClientSecretOAuthScopes @("Get-GSUser", "Google.Apis.Calendar.v3.CalendarService") -AdminEmail "user@domain.com" -SetAsDefaultConfig + Set-PSGSuiteConfig -ConfigName "personal" -ClientSecretsPath "C:\Keys\Client_Secret.json" -ClientSecretScopes @("Get-GSUser", "Google.Apis.Calendar.v3.CalendarService") -AdminEmail "user@domain.com" -SetAsDefaultConfig This builds a config named "personal" and sets it as the default config. Client Secrets authentication is used and the OAuth scopes used by the "Get-GSUser" function and the Google Calendar API service are specified for use during OAuth authentication. #> @@ -136,9 +136,9 @@ function Set-PSGSuiteConfig { [string] $ClientSecrets, [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] - [ValidateSet([PSGSuiteValidClientSecretOAuthScopeValues])] + [ValidateSet([PSGSuiteValidScopeIdentifierValues])] [string[]] - $ClientSecretOAuthScopes, + $ClientSecretScopes, [parameter(Mandatory = $false,ValueFromPipelineByPropertyName = $true)] [string] $AppEmail, @@ -188,7 +188,7 @@ function Set-PSGSuiteConfig { } } Write-Verbose "Setting config name '$ConfigName'" - $configParams = @('P12Key','P12KeyPath','P12KeyPassword','JSONServiceAccountKeyPath','JSONServiceAccountKey','ClientSecretsPath','ClientSecrets','ClientSecretOAuthScopes','AppEmail','AdminEmail','CustomerID','Domain','Preference','ServiceAccountClientID','Webhook','Space') + $configParams = @('P12Key','P12KeyPath','P12KeyPassword','JSONServiceAccountKeyPath','JSONServiceAccountKey','ClientSecretsPath','ClientSecrets','ClientSecretScopes','AppEmail','AdminEmail','CustomerID','Domain','Preference','ServiceAccountClientID','Webhook','Space') if ($SetAsDefaultConfig -or !$configHash["DefaultConfig"]) { $configHash["DefaultConfig"] = $ConfigName } @@ -226,10 +226,10 @@ function Set-PSGSuiteConfig { $configHash["$ConfigName"]['ClientSecrets'] = (Invoke-GSEncrypt $(Get-Content $PSBoundParameters[$key] -Raw)) } } - ClientSecretOAuthScopes { - $configHash["$ConfigName"]['ClientSecretOAuthScopes'] = @() + ClientSecretScopes { + $configHash["$ConfigName"]['ClientSecretScopes'] = @() foreach ($Entry in $PSBoundParameters[$key]){ - $configHash["$ConfigName"]['ClientSecretOAuthScopes'] += Invoke-GSEncrypt $Entry + $configHash["$ConfigName"]['ClientSecretScopes'] += Invoke-GSEncrypt $Entry } } Webhook { diff --git a/ci/templates/OAuthScopes.ps1 b/ci/templates/OAuthScopes.ps1 index 3402e964..c6a9cb07 100644 --- a/ci/templates/OAuthScopes.ps1 +++ b/ci/templates/OAuthScopes.ps1 @@ -17,7 +17,7 @@ Param( 5. If any API services or OAuth scopes were found for the referenced function, they are copied to the current function. The items found for all public functions are used to generate the PowerShell code that defines the following items: - - $script:_PSGSuiteOAuthScopes - This is an array that contains all items that were found. + - $script:_PSGSuiteScopes - This is an array that contains all items that were found. - class [PSGSuiteValidServiceValues] - This is a parameter validation class that contains all Google API services that were found. - class [PSGSuiteValidFunctionValues] - This is a parameter validation class that contains all of the public PSGSuite function names. - class [PSGSuiteValidOAuthScopeValues] - This is a parameter validation class that contains all of the OAuth scopes that were found. @@ -184,6 +184,19 @@ ForEach ($FunctionName in $Script:FunctionScopes.keys){ } } +# Manual scope entry for the mandatory userinfo.email and openid scopes +# They are required for validating the owner of generated UserCredentials +$OutputScopes += [PSCustomObject]@{ + 'Function' = $null + 'Service' = $null + 'Scope' = 'https://www.googleapis.com/auth/userinfo.email' +} +$OutputScopes += [PSCustomObject]@{ + 'Function' = $null + 'Service' = $null + 'Scope' = 'openid' +} + # Generate datasets that will be used to validate function parameters $OutputScopes = $OutputScopes | Sort-Object -Property Service, Function, Scope $ValidServices = $OutputScopes | Select-Object -ExpandProperty 'Service' -Unique | Sort-Object @@ -201,8 +214,8 @@ $HashOutput = @{} # \Module\OAuthScopes.ps1 $Code = @" -# Scope data that is used by the Get-PSGSuiteOAuthScope function. -`$script:_PSGSuiteOAuthScopes = @' +# Scope data that is used by the Get-PSGSuiteScope function. +`$script:_PSGSuiteScopes = @' $($OutputScopes | ConvertTo-Json) '@ | ConvertFrom-Json "@ @@ -254,9 +267,9 @@ class PSGSuiteValidOAuthScopeValues : System.Management.Automation.IValidateSetV $HashOutput['\Class\PSGSuiteValidOAuthScopeValues.ps1'] = $Code -# \Class\PSGSuiteValidClientSecretOAuthScopeValues +# \Class\PSGSuiteValidScopeIdentifierValues $Code = @" -class PSGSuiteValidClientSecretOAuthScopeValues : System.Management.Automation.IValidateSetValuesGenerator { +class PSGSuiteValidScopeIdentifierValues : System.Management.Automation.IValidateSetValuesGenerator { [string[]] GetValidValues() { `$Values = @( '$($ValidAllValues -join "',`n '")' @@ -265,7 +278,7 @@ class PSGSuiteValidClientSecretOAuthScopeValues : System.Management.Automation.I } } "@ -$HashOutput['\Class\PSGSuiteValidClientSecretOAuthScopeValues.ps1'] = $Code +$HashOutput['\Class\PSGSuiteValidScopeIdentifierValues.ps1'] = $Code $HashOutput From 656411e19cfaa009bd0bcfe3b790e88058b68824 Mon Sep 17 00:00:00 2001 From: Foggy2 <23325075+Foggy2@users.noreply.github.com> Date: Sun, 27 Jul 2025 21:36:32 +0930 Subject: [PATCH 13/13] Cached credentials are cleared when loading a new config. Credentials are stored on disk in module directories. --- PSGSuite/Module/Initialization.ps1 | 5 +--- .../New-GoogleUserCredential.ps1 | 29 ++++++++----------- .../Public/Authentication/Get-GSScope.ps1 | 18 ++++-------- .../Public/Authentication/Revoke-GSToken.ps1 | 5 ++-- .../Configuration/Get-PSGSuiteConfig.ps1 | 13 +++++++++ 5 files changed, 35 insertions(+), 35 deletions(-) diff --git a/PSGSuite/Module/Initialization.ps1 b/PSGSuite/Module/Initialization.ps1 index 73c5d86b..fa81a090 100644 --- a/PSGSuite/Module/Initialization.ps1 +++ b/PSGSuite/Module/Initialization.ps1 @@ -107,7 +107,4 @@ Get-PSGSuiteConfig -Path '$ModuleRoot\$env:USERNAME-$env:COMPUTERNAME-$env:PSGSu } catch { Write-Warning "There was no config returned! Please make sure you are using the correct key or have a configuration already saved." -} - -# File path to the directory where Google OAuth tokens are persisted on disk -$Script:_PSGSuiteCredPath = Join-Path (Resolve-Path (Join-Path "~" ".scrthq")) "PSGSuite" \ No newline at end of file +} \ No newline at end of file diff --git a/PSGSuite/Private/Authentication/New-GoogleUserCredential.ps1 b/PSGSuite/Private/Authentication/New-GoogleUserCredential.ps1 index a3242acc..749cf0c4 100644 --- a/PSGSuite/Private/Authentication/New-GoogleUserCredential.ps1 +++ b/PSGSuite/Private/Authentication/New-GoogleUserCredential.ps1 @@ -190,12 +190,7 @@ function New-GoogleUserCredential { Write-Verbose "Generating UserCredential for user '$user' in '$($PSCmdlet.ParameterSetName)' mode." - # The $TokenKey is used as the key when retrieving tokens (UserCredentials) from disk. - # We will create the key such that all tokens will be unique per PSGSuite config and user combination. Allowing the same user to have different - # tokens per PSGSuite configuration. - # https://github.com/googleapis/google-api-dotnet-client/issues/2709 - $TokenKey = @($script:PSGSuite.ConfigName, $User) -Join '-' - $TokenName = [Google.Apis.Util.Store.FileDataStore]::GenerateStoredKey($TokenKey, [Google.Apis.Auth.OAuth2.Responses.TokenResponse]) + $TokenName = [Google.Apis.Util.Store.FileDataStore]::GenerateStoredKey($User, [Google.Apis.Auth.OAuth2.Responses.TokenResponse]) $TokenPath = Join-Path $Datastore.FolderPath $TokenName @@ -239,17 +234,17 @@ function New-GoogleUserCredential { # Find the existing credential if one exists # # Search in memory first. Otherwise try the disk datastore. - $ExistingCredential = If ($script:_PSGSuiteUserCredentials.containsKey($TokenKey)){ + $ExistingCredential = If ($script:_PSGSuiteUserCredentials.containsKey($User)){ - Write-Verbose "Getting existing UserCredential '$TokenKey' from memory" - $script:_PSGSuiteUserCredentials[$TokenKey] + Write-Verbose "Getting existing UserCredential for '$User' from memory" + $script:_PSGSuiteUserCredentials[$User] } else { If ((Test-Path $TokenPath)){ # Online flows - Load the existing credential If ($PSCmdlet.ParameterSetName -ne "Offline"){ - Write-Verbose "Invoking the offline flow to get existing UserCredential '$TokenKey' from disk" + Write-Verbose "Invoking the offline flow to get existing UserCredential for '$User' from disk" New-GoogleUserCredential -User $User -Offline } } else { @@ -290,7 +285,7 @@ function New-GoogleUserCredential { } } } else { - Write-Verbose "Existing UserCredential '$TokenKey' not found." + Write-Verbose "Existing UserCredential for '$User' not found." } } @@ -423,8 +418,8 @@ function New-GoogleUserCredential { # Online flows - Revoke existing OAuth Token # # If the token scopes are changing a new token will be generated. - # When creating a UserCredential the GoogleWebAuthorizationBroker loads the existing credential from disk (based on $tokenKey) without verifying if all scopes exist. - # To force fetching a new UserCredential from Google we need to make sure no entry in the diskDatastore exists for $tokenKey. + # When creating a UserCredential the GoogleWebAuthorizationBroker loads the existing credential from disk without verifying if all scopes exist. + # To force fetching a new UserCredential from Google we need to make sure no entry in the diskDatastore exists for $User. if ($ExistingCredential){ Write-Verbose "Revoking the existing UserCredential." Revoke-GSToken -UserCredential $ExistingCredential -confirm:$False @@ -435,7 +430,7 @@ function New-GoogleUserCredential { # Online flows - Request the new OAuth Token # Offline flow - Load the existing OAuth token from disk Try { - Write-Verbose "Building UserCredential '$TokenKey' from ClientSecrets and prompting for authorization if necessary." + Write-Verbose "Building UserCredential for '$User' from ClientSecrets and prompting for authorization if necessary." $Initializer = [Google.Apis.Auth.OAuth2.Flows.GoogleAuthorizationCodeFlow+Initializer]::new() $Initializer.ClientSecrets = $script:_PSGSuiteClientSecrets $Initializer.LoginHint = $User @@ -447,7 +442,7 @@ function New-GoogleUserCredential { $credential = [Google.Apis.Auth.OAuth2.GoogleWebAuthorizationBroker]::AuthorizeAsync( $Initializer, [string[]]@($ScopesToRequest), - $TokenKey, + $User, [System.Threading.CancellationToken]::None, $Datastore, $CodeReceiver @@ -460,7 +455,7 @@ function New-GoogleUserCredential { If ($null -eq $Credential){ $PSCmdlet.ThrowTerminatingError((ThrowTerm "Failed to create UserCredential. The authorisation prompt was likely cancelled, please try the request again.")) } - Write-Verbose "UserCredential '$TokenKey' has been created" + Write-Verbose "UserCredential for '$User' has been created" @@ -546,7 +541,7 @@ function New-GoogleUserCredential { } # Save the newly created credential in memory for later use - $script:_PSGSuiteUserCredentials[$tokenKey] = $credential + $script:_PSGSuiteUserCredentials[$User] = $credential Write-Verbose "UserCredential for user '$user' was successfully created and includes $($TokenScopes.count) authorised OAuth scopes." Return $Credential diff --git a/PSGSuite/Public/Authentication/Get-GSScope.ps1 b/PSGSuite/Public/Authentication/Get-GSScope.ps1 index 542cc532..578dc0e8 100644 --- a/PSGSuite/Public/Authentication/Get-GSScope.ps1 +++ b/PSGSuite/Public/Authentication/Get-GSScope.ps1 @@ -46,19 +46,13 @@ Function Get-GSScope { Resolve-Email ([ref]$User) - # The $TokenKey is used as the key when retrieving tokens from disk or the in-memory user credential cache. - # We will create the key such that all tokens will be unique per PSGSuite config and user combination. Allowing the same user to have different - # tokens per PSGSuite configuration. - # https://github.com/googleapis/google-api-dotnet-client/issues/2709 - $TokenKey = @($script:PSGSuite.ConfigName, $User) -Join '-' - Write-Verbose "Getting the authorized OAuth scopes for user '$user'" # Try the memory cache first before fetching from disk $AuthorizedToken = If ($script:_PSGSuiteUserCredentials){ - If ($script:_PSGSuiteUserCredentials.ContainsKey($TokenKey)){ - Write-Verbose "UserCredential '$TokenKey' was found in memory" - $script:_PSGSuiteUserCredentials[$TokenKey].Token + If ($script:_PSGSuiteUserCredentials.ContainsKey($User)){ + Write-Verbose "UserCredential for '$User' was found in memory" + $script:_PSGSuiteUserCredentials[$User].Token } } @@ -67,9 +61,9 @@ Function Get-GSScope { # Initialize the FileDataStore used for storing and retrieving cached OAuth tokens. $Datastore = New-Object 'Google.Apis.Util.Store.FileDataStore' -ArgumentList $Script:_PSGSuiteCredPath,$true # Search the datastore - $AuthorizedToken = $Datastore.getAsync[Google.Apis.Auth.OAuth2.Responses.TokenResponse]($TokenKey).GetAwaiter().GetResult() + $AuthorizedToken = $Datastore.getAsync[Google.Apis.Auth.OAuth2.Responses.TokenResponse]($User).GetAwaiter().GetResult() If ($AuthorizedToken){ - Write-Verbose "UserCredential '$TokenKey' was found on disk" + Write-Verbose "UserCredential for '$User' was found on disk" } } @@ -84,7 +78,7 @@ Function Get-GSScope { } } else { - Write-Verbose "UserCredential '$TokenKey' does not exist" + Write-Verbose "UserCredential for '$User' does not exist" } } diff --git a/PSGSuite/Public/Authentication/Revoke-GSToken.ps1 b/PSGSuite/Public/Authentication/Revoke-GSToken.ps1 index 549c7fb8..84f075b0 100644 --- a/PSGSuite/Public/Authentication/Revoke-GSToken.ps1 +++ b/PSGSuite/Public/Authentication/Revoke-GSToken.ps1 @@ -60,9 +60,10 @@ Function Revoke-GSToken { Try { If ($NoDelete){ - # To prevent deleting the token from the disk cache we will call revoke with a non-existent TokenKey that will silently fail on deletion + # To prevent deleting the token from the disk cache we will call revoke with a non-existent UserID (key) that will silently fail on deletion # Ideally we would call $UserCredential.Flow.RevokeTokenAsync('NonexistentKey', $AccessToken, ...) but Flow is not Common Language Specification (CLS) compliant and errors out. - # So instead we will repackage the token into a new UserCredential referencing a non-existent key, and trigger revocation from there. + # So instead we will repackage the token into a new UserCredential located in memory referencing a non-existent userID (key), and trigger revocation from there. + # The userId property of the credential is used to build the path to the credential in the FileDataStore, it is not used for issuing any API commands. $repackagedCredential = [Google.Apis.Auth.OAuth2.UserCredential]::new($UserCredential.Flow, 'NonexistentKey', $UserCredential.Token) $repackagedCredential.RevokeTokenAsync([System.Threading.CancellationToken]::None).GetAwaiter().GetResult() | Out-Null Write-Verbose "Successfully revoked UserCredential '$($UserCredential.UserId)' and persisted on disk." diff --git a/PSGSuite/Public/Configuration/Get-PSGSuiteConfig.ps1 b/PSGSuite/Public/Configuration/Get-PSGSuiteConfig.ps1 index 60690dbf..7cffe8e9 100644 --- a/PSGSuite/Public/Configuration/Get-PSGSuiteConfig.ps1 +++ b/PSGSuite/Public/Configuration/Get-PSGSuiteConfig.ps1 @@ -86,7 +86,20 @@ function Get-PSGSuiteConfig { $decryptedConfig = $encConf | Get-GSDecryptedConfig @decryptParams Write-Verbose "Retrieved configuration '$choice'" if (!$NoImport) { + + # Clear cached user credentials + If ($script:PSGSuite){ + write-verbose 'Clearing cached credentials from memory' + Clear-PSGSuiteServiceCache + If ($script:_PSGSuiteUserCredentials){ + $script:_PSGSuiteUserCredentials.Clear() + } + } + $script:PSGSuite = $decryptedConfig + + # File path to the directory where Google OAuth tokens are persisted on disk + $Script:_PSGSuiteCredPath = Join-Path (Resolve-Path (Join-Path "~" ".scrthq")) "PSGSuite" $Script:PSGSuite.ConfigName } if ($PassThru) { $decryptedConfig